@gooddata/sdk-ui-kit 11.40.0-alpha.1 → 11.40.0-alpha.3

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 (35) hide show
  1. package/esm/@ui/@types/icon.d.ts +1 -1
  2. package/esm/@ui/UiAsyncTable/UiAsyncTable/UiAsyncTableCheckbox.js +7 -1
  3. package/esm/@ui/UiAsyncTable/UiAsyncTable/UiAsyncTableDropdownItem.js +2 -2
  4. package/esm/@ui/UiCheckbox/UiCheckbox.d.ts +13 -1
  5. package/esm/@ui/UiCheckbox/UiCheckbox.js +14 -4
  6. package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.d.ts +32 -0
  7. package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.js +31 -0
  8. package/esm/@ui/UiIcon/icons.js +3 -0
  9. package/esm/@ui/UiLabelChecklistRow/UiLabelChecklistRow.d.ts +33 -0
  10. package/esm/@ui/UiLabelChecklistRow/UiLabelChecklistRow.js +26 -0
  11. package/esm/@ui/UiLabelRow/UiLabelRow.d.ts +52 -0
  12. package/esm/@ui/UiLabelRow/UiLabelRow.js +18 -0
  13. package/esm/@ui/UiLabelsList/UiLabelsList.d.ts +30 -0
  14. package/esm/@ui/UiLabelsList/UiLabelsList.js +24 -0
  15. package/esm/@ui/UiLabelsPicker/UiLabelsPicker.d.ts +63 -0
  16. package/esm/@ui/UiLabelsPicker/UiLabelsPicker.js +89 -0
  17. package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts +43 -0
  18. package/esm/@ui/UiPermissionMenu/UiPermissionMenu.js +113 -0
  19. package/esm/@ui/UiPopover/UiPopover.d.ts +14 -2
  20. package/esm/@ui/UiPopover/UiPopover.js +21 -5
  21. package/esm/index.d.ts +6 -0
  22. package/esm/index.js +6 -0
  23. package/esm/locales.d.ts +54 -0
  24. package/esm/locales.js +22 -0
  25. package/esm/sdk-ui-kit.d.ts +289 -3
  26. package/package.json +11 -11
  27. package/src/@ui/UiCheckbox/UiCheckbox.scss +29 -8
  28. package/src/@ui/UiGranteeRowControls/UiGranteeRowControls.scss +8 -0
  29. package/src/@ui/UiLabelRow/UiLabelRow.scss +40 -0
  30. package/src/@ui/UiLabelsList/UiLabelsList.scss +21 -0
  31. package/src/@ui/UiLabelsPicker/UiLabelsPicker.scss +29 -0
  32. package/src/@ui/UiPermissionMenu/UiPermissionMenu.scss +66 -0
  33. package/src/@ui/index.scss +5 -0
  34. package/styles/css/main.css +158 -7
  35. package/styles/css/main.css.map +1 -1
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useRef } from "react";
4
+ import { useIntl } from "react-intl";
5
+ import { olpPermissionMessages } from "../../locales.js";
6
+ import { bem } from "../@utils/bem.js";
7
+ import { UiIconButton } from "../UiIconButton/UiIconButton.js";
8
+ import { UiPopover } from "../UiPopover/UiPopover.js";
9
+ import { UiTooltip } from "../UiTooltip/UiTooltip.js";
10
+ const { b, e } = bem("gd-ui-kit-permission-menu");
11
+ /**
12
+ * Per-grantee permission popover. Renders a fixed set of rows — two
13
+ * permission levels (Can view & share / Can view), an optional divider,
14
+ * and optional Transfer ownership / Labels / Remove access action rows.
15
+ * Each level row carries an `infoCircle` tooltip.
16
+ *
17
+ * @internal
18
+ */
19
+ export function UiPermissionMenu({ anchor, selectedLevel, onPermissionChange, onTransferOwnership, onLabelsClick, labelsCounter, onRemoveAccess, dataTestId, }) {
20
+ return (_jsx(UiPopover, { anchor: anchor, anchorAccessibilityConfig: { ariaHaspopup: "menu" }, width: 180, content: ({ onClose }) => (_jsx(MenuBody, { selectedLevel: selectedLevel, onPermissionChange: onPermissionChange, onTransferOwnership: onTransferOwnership, onLabelsClick: onLabelsClick, labelsCounter: labelsCounter, onRemoveAccess: onRemoveAccess, onClose: onClose, dataTestId: dataTestId })) }));
21
+ }
22
+ function MenuBody({ selectedLevel, onPermissionChange, onTransferOwnership, onLabelsClick, labelsCounter, onRemoveAccess, onClose, dataTestId, }) {
23
+ const intl = useIntl();
24
+ const choose = (next) => () => {
25
+ next();
26
+ onClose();
27
+ };
28
+ const levelItems = [
29
+ {
30
+ key: "SHARE",
31
+ label: intl.formatMessage(olpPermissionMessages.canViewAndShare),
32
+ tooltip: intl.formatMessage(olpPermissionMessages.canViewAndShareTooltip),
33
+ radioValue: "SHARE",
34
+ onClick: choose(() => onPermissionChange("SHARE")),
35
+ },
36
+ {
37
+ key: "VIEW",
38
+ label: intl.formatMessage(olpPermissionMessages.canView),
39
+ tooltip: intl.formatMessage(olpPermissionMessages.canViewTooltip),
40
+ radioValue: "VIEW",
41
+ onClick: choose(() => onPermissionChange("VIEW")),
42
+ },
43
+ ];
44
+ const actionItems = [];
45
+ if (onTransferOwnership) {
46
+ actionItems.push({
47
+ key: "transfer",
48
+ label: intl.formatMessage(olpPermissionMessages.transferOwnership),
49
+ onClick: choose(onTransferOwnership),
50
+ });
51
+ }
52
+ if (onLabelsClick) {
53
+ actionItems.push({
54
+ key: "labels",
55
+ label: intl.formatMessage(olpPermissionMessages.labels),
56
+ counter: labelsCounter,
57
+ onClick: choose(onLabelsClick),
58
+ });
59
+ }
60
+ if (onRemoveAccess) {
61
+ actionItems.push({
62
+ key: "remove",
63
+ label: intl.formatMessage(olpPermissionMessages.removeAccess),
64
+ onClick: choose(onRemoveAccess),
65
+ });
66
+ }
67
+ const menuRef = useRef(null);
68
+ const handleKeyDown = (event) => {
69
+ const root = menuRef.current;
70
+ if (!root)
71
+ return;
72
+ const focusable = Array.from(root.querySelectorAll('button[role^="menuitem"]'));
73
+ const currentIndex = focusable.indexOf(document.activeElement);
74
+ if (currentIndex < 0)
75
+ return;
76
+ let nextIndex = null;
77
+ switch (event.key) {
78
+ case "ArrowDown":
79
+ nextIndex = (currentIndex + 1) % focusable.length;
80
+ break;
81
+ case "ArrowUp":
82
+ nextIndex = (currentIndex - 1 + focusable.length) % focusable.length;
83
+ break;
84
+ case "Home":
85
+ nextIndex = 0;
86
+ break;
87
+ case "End":
88
+ nextIndex = focusable.length - 1;
89
+ break;
90
+ default:
91
+ return;
92
+ }
93
+ event.preventDefault();
94
+ focusable[nextIndex]?.focus();
95
+ };
96
+ return (_jsx("div", { className: b(), "data-testid": dataTestId, children: _jsxs("div", { ref: menuRef, className: e("items"), role: "menu", "aria-orientation": "vertical", "aria-label": intl.formatMessage(olpPermissionMessages.menuLabel), onKeyDown: handleKeyDown, children: [levelItems.map((item) => (_jsx(PermissionMenuItem, { item: item, selectedLevel: selectedLevel }, item.key))), actionItems.length > 0 ? _jsx("div", { className: e("divider"), role: "separator" }) : null, actionItems.map((item) => (_jsx(PermissionMenuItem, { item: item, selectedLevel: selectedLevel }, item.key)))] }) }));
97
+ }
98
+ function PermissionMenuItem({ item, selectedLevel }) {
99
+ const intl = useIntl();
100
+ // Tooltip anchor must live OUTSIDE the menu-item button so we don't nest an
101
+ // interactive element inside a button (invalid HTML, breaks focus). The row
102
+ // wrapper provides the flex layout; the button covers label + counter; the
103
+ // tooltip sits next to the button.
104
+ const isRadio = !!item.radioValue;
105
+ const isChecked = isRadio && item.radioValue === selectedLevel;
106
+ return (_jsxs("div", { className: e("item-row"), children: [
107
+ _jsxs("button", { type: "button", role: isRadio ? "menuitemradio" : "menuitem", "aria-checked": isRadio ? isChecked : undefined, className: e("item"), onClick: item.onClick, children: [
108
+ _jsx("span", { className: e("item-label"), children: item.label }), item.counter ? _jsx("span", { className: e("item-counter"), children: item.counter }) : null] }), item.tooltip ? (_jsx(UiTooltip, { triggerBy: ["hover", "focus"], content: item.tooltip, anchor: _jsx(UiIconButton, { icon: "infoCircle", variant: "tertiary", size: "small", accessibilityConfig: {
109
+ ariaLabel: intl.formatMessage(olpPermissionMessages.moreInfoAriaLabel, {
110
+ label: item.label,
111
+ }),
112
+ } }) })) : null] }));
113
+ }
@@ -1,4 +1,4 @@
1
- import { type ReactElement, type ReactNode, type RefObject } from "react";
1
+ import { type AriaAttributes, type ReactElement, type ReactNode, type RefObject } from "react";
2
2
  import { type IAccessibilityConfigBase } from "../../typings/accessibility.js";
3
3
  import { type IUiTooltipProps } from "../UiTooltip/types.js";
4
4
  /**
@@ -7,6 +7,18 @@ import { type IUiTooltipProps } from "../UiTooltip/types.js";
7
7
  export interface IUiPopoverProps {
8
8
  id?: string;
9
9
  anchor: ReactElement<any>;
10
+ /**
11
+ * Accessibility attributes forwarded to the anchor element (the trigger).
12
+ * `ariaHaspopup` defaults to `"dialog"` — pass
13
+ * `"menu"` for menu-style popovers, `"listbox"` for
14
+ * single-select pickers, etc. Set `ariaHaspopup: false` to
15
+ * suppress. (Separate from `accessibilityConfig`, which targets
16
+ * the popover content surface.)
17
+ */
18
+ anchorAccessibilityConfig?: {
19
+ ariaHaspopup?: "dialog" | "menu" | "listbox" | "tree" | "grid" | false;
20
+ ariaControls?: AriaAttributes["aria-controls"];
21
+ };
10
22
  width?: "default" | number;
11
23
  disabled?: boolean;
12
24
  tabIndex?: number;
@@ -40,4 +52,4 @@ export interface IUiPopoverProps {
40
52
  /**
41
53
  * @internal
42
54
  */
43
- export declare function UiPopover({ id, accessibilityConfig, anchor, width, title, tabIndex, disabled, content, footer, closeText, closeVisible, initialFocus, returnFocusTo, triggerBy, returnFocusAfterClose, focusCheckFn, enableFocusTrap, onOpen, onClose }: IUiPopoverProps): import("react/jsx-runtime").JSX.Element;
55
+ export declare function UiPopover({ id, accessibilityConfig, anchor, anchorAccessibilityConfig, width, title, tabIndex, disabled, content, footer, closeText, closeVisible, initialFocus, returnFocusTo, triggerBy, returnFocusAfterClose, focusCheckFn, enableFocusTrap, onOpen, onClose }: IUiPopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -9,22 +9,39 @@ import { defaultFocusCheckFn } from "../UiFocusManager/utils.js";
9
9
  import { UiTooltip } from "../UiTooltip/UiTooltip.js";
10
10
  const { b, e } = bem("gd-ui-kit-popover");
11
11
  const defaultAccessibilityConfig = {};
12
+ function buildAnchorAriaAttrs(cfg) {
13
+ const ariaHaspopup = cfg?.ariaHaspopup ?? "dialog";
14
+ const out = {};
15
+ if (ariaHaspopup !== false) {
16
+ out["aria-haspopup"] = ariaHaspopup;
17
+ }
18
+ if (cfg?.ariaControls) {
19
+ out["aria-controls"] = cfg.ariaControls;
20
+ }
21
+ return out;
22
+ }
12
23
  /**
13
24
  * @internal
14
25
  */
15
- export function UiPopover({ id, accessibilityConfig = defaultAccessibilityConfig, anchor, width = "default", title, tabIndex, disabled, content, footer, closeText, closeVisible, initialFocus, returnFocusTo, triggerBy = ["click"], returnFocusAfterClose = true, focusCheckFn = defaultFocusCheckFn, enableFocusTrap = false, onOpen, onClose, }) {
26
+ export function UiPopover({ id, accessibilityConfig = defaultAccessibilityConfig, anchor, anchorAccessibilityConfig, width = "default", title, tabIndex, disabled, content, footer, closeText, closeVisible, initialFocus, returnFocusTo, triggerBy = ["click"], returnFocusAfterClose = true, focusCheckFn = defaultFocusCheckFn, enableFocusTrap = false, onOpen, onClose, }) {
16
27
  const ref = useRef(null);
17
28
  const returnFocus = useMemo(() => {
18
29
  return returnFocusTo ?? ref;
19
30
  }, [returnFocusTo]);
20
31
  const currentId = useIdPrefixed("popover");
21
32
  const titleId = useIdPrefixed("popover-title");
33
+ // Only point aria-labelledby at the title element when one will actually
34
+ // render — i.e. the caller supplied a title. Without this guard, titleless
35
+ // popovers (e.g. UiPermissionMenu) emit a dangling reference. Caller-supplied
36
+ // ariaLabel / ariaLabelledBy in accessibilityConfig still takes precedence.
37
+ const titleLabelledBy = title ? titleId : undefined;
22
38
  return (_jsx(UiTooltip, { onOpen: onOpen, onClose: onClose, accessibilityConfig: {
39
+ ariaLabelledBy: titleLabelledBy,
23
40
  ...accessibilityConfig,
24
41
  role: "dialog",
25
- ariaLabelledBy: titleId,
26
42
  }, anchor: cloneElement(anchor, {
27
43
  ...(tabIndex === undefined ? {} : { tabIndex }),
44
+ ...buildAnchorAriaAttrs(anchorAccessibilityConfig),
28
45
  ref: mergeRefs(ref, anchor.props?.ref),
29
46
  }), offset: 0, content: ({ onClose, type }) => {
30
47
  //NOTE: Do not make screen reader tooltip visible, live content is enough
@@ -37,10 +54,9 @@ export function UiPopover({ id, accessibilityConfig = defaultAccessibilityConfig
37
54
  ? undefined
38
55
  : () => {
39
56
  onClose();
40
- }, children: _jsxs("div", { id: id ?? currentId, tabIndex: 0, className: b(), style: { width: width === "default" ? undefined : width }, children: [
41
- _jsxs("div", { className: e("header"), children: [typeof title === "string" ? (_jsx("div", { id: titleId, className: e("header-title"), children: title })) : (_jsx("div", { id: titleId, children: title })), closeVisible ? (_jsx("div", { className: e("header-close"), children: _jsx(UiButton, { label: "", size: "small", variant: "tertiary", iconBefore: "close", accessibilityConfig: {
57
+ }, children: _jsxs("div", { id: id ?? currentId, tabIndex: 0, className: b(), style: { width: width === "default" ? undefined : width }, children: [title || closeVisible ? (_jsxs("div", { className: e("header"), children: [title ? (typeof title === "string" ? (_jsx("div", { id: titleId, className: e("header-title"), children: title })) : (_jsx("div", { id: titleId, children: title }))) : null, closeVisible ? (_jsx("div", { className: e("header-close"), children: _jsx(UiButton, { label: "", size: "small", variant: "tertiary", iconBefore: "close", accessibilityConfig: {
42
58
  ariaLabel: closeText,
43
- }, onClick: onClose }) })) : null] }), content ? (_jsx("div", { className: e("content"), children: typeof content === "function" ? content({ onClose }) : content })) : null, footer ? (_jsx("div", { className: e("footer"), children: typeof footer === "function" ? footer({ onClose }) : footer })) : null] }) }));
59
+ }, onClick: onClose }) })) : null] })) : null, content ? (_jsx("div", { className: e("content"), children: typeof content === "function" ? content({ onClose }) : content })) : null, footer ? (_jsx("div", { className: e("footer"), children: typeof footer === "function" ? footer({ onClose }) : footer })) : null] }) }));
44
60
  }, variant: "none", behaviour: "popover", triggerBy: triggerBy, showArrow: false, optimalPlacement: true, arrowPlacement: "top-start", disabled: disabled }));
45
61
  }
46
62
  function mergeRefs(...refs) {
package/esm/index.d.ts CHANGED
@@ -462,3 +462,9 @@ export { UiGranteeRow, type IUiGranteeRowProps } from "./@ui/UiGranteeRow/UiGran
462
462
  export { UiObjectShareDialog, type IUiObjectShareDialogProps, type IUiObjectShareDialogGrantee, } from "./@ui/UiObjectShareDialog/UiObjectShareDialog.js";
463
463
  export { UiAddGranteeDialog, type IUiAddGranteeDialogProps, } from "./@ui/UiAddGranteeDialog/UiAddGranteeDialog.js";
464
464
  export { UiGeneralAccessRadio, type IUiGeneralAccessRadioProps, type GeneralAccessValue, } from "./@ui/UiGeneralAccessRadio/UiGeneralAccessRadio.js";
465
+ export { UiLabelRow, type IUiLabelRowProps, type LabelRowKind } from "./@ui/UiLabelRow/UiLabelRow.js";
466
+ export { UiLabelChecklistRow, type IUiLabelChecklistRowProps, } from "./@ui/UiLabelChecklistRow/UiLabelChecklistRow.js";
467
+ export { UiLabelsList, type IUiLabelsListProps, type IUiLabelsListItem, } from "./@ui/UiLabelsList/UiLabelsList.js";
468
+ export { UiLabelsPicker, isLabelsPickerItemChecked, type IUiLabelsPickerProps, type IUiLabelsPickerItem, } from "./@ui/UiLabelsPicker/UiLabelsPicker.js";
469
+ export { UiPermissionMenu, type IUiPermissionMenuProps, type PermissionMenuLevel, } from "./@ui/UiPermissionMenu/UiPermissionMenu.js";
470
+ export { UiGranteeRowControls, type IUiGranteeRowControlsProps, } from "./@ui/UiGranteeRowControls/UiGranteeRowControls.js";
package/esm/index.js CHANGED
@@ -417,3 +417,9 @@ export { UiGranteeRow } from "./@ui/UiGranteeRow/UiGranteeRow.js";
417
417
  export { UiObjectShareDialog, } from "./@ui/UiObjectShareDialog/UiObjectShareDialog.js";
418
418
  export { UiAddGranteeDialog, } from "./@ui/UiAddGranteeDialog/UiAddGranteeDialog.js";
419
419
  export { UiGeneralAccessRadio, } from "./@ui/UiGeneralAccessRadio/UiGeneralAccessRadio.js";
420
+ export { UiLabelRow } from "./@ui/UiLabelRow/UiLabelRow.js";
421
+ export { UiLabelChecklistRow, } from "./@ui/UiLabelChecklistRow/UiLabelChecklistRow.js";
422
+ export { UiLabelsList, } from "./@ui/UiLabelsList/UiLabelsList.js";
423
+ export { UiLabelsPicker, isLabelsPickerItemChecked, } from "./@ui/UiLabelsPicker/UiLabelsPicker.js";
424
+ export { UiPermissionMenu, } from "./@ui/UiPermissionMenu/UiPermissionMenu.js";
425
+ export { UiGranteeRowControls, } from "./@ui/UiGranteeRowControls/UiGranteeRowControls.js";
package/esm/locales.d.ts CHANGED
@@ -80,6 +80,9 @@ export declare const granularPermissionMessageTooltips: {
80
80
  };
81
81
  };
82
82
  export declare const commonDialogMessages: {
83
+ apply: {
84
+ id: string;
85
+ };
83
86
  cancel: {
84
87
  id: string;
85
88
  };
@@ -148,3 +151,54 @@ export declare const olpGeneralAccessMessages: {
148
151
  id: string;
149
152
  };
150
153
  };
154
+ export declare const olpLabelMessages: {
155
+ suffixPrimary: {
156
+ id: string;
157
+ };
158
+ suffixDefault: {
159
+ id: string;
160
+ };
161
+ listHeading: {
162
+ id: string;
163
+ };
164
+ popoverTitle: {
165
+ id: string;
166
+ };
167
+ };
168
+ export declare const olpPermissionMessages: {
169
+ canViewAndShare: {
170
+ id: string;
171
+ };
172
+ canView: {
173
+ id: string;
174
+ };
175
+ canViewAndShareTooltip: {
176
+ id: string;
177
+ };
178
+ canViewTooltip: {
179
+ id: string;
180
+ };
181
+ transferOwnership: {
182
+ id: string;
183
+ };
184
+ labels: {
185
+ id: string;
186
+ };
187
+ removeAccess: {
188
+ id: string;
189
+ };
190
+ menuLabel: {
191
+ id: string;
192
+ };
193
+ moreInfoAriaLabel: {
194
+ id: string;
195
+ };
196
+ };
197
+ export declare const olpGranteeControlsMessages: {
198
+ allLabels: {
199
+ id: string;
200
+ };
201
+ labelsCount: {
202
+ id: string;
203
+ };
204
+ };
package/esm/locales.js CHANGED
@@ -43,6 +43,7 @@ export const granularPermissionMessageTooltips = defineMessages({
43
43
  cannotGrantLowerForGroup: { id: "shareDialog.share.granular.granularGroup.tooltip.cannotGrantLower" },
44
44
  });
45
45
  export const commonDialogMessages = defineMessages({
46
+ apply: { id: "apply" },
46
47
  cancel: { id: "cancel" },
47
48
  close: { id: "close" },
48
49
  });
@@ -73,3 +74,24 @@ export const olpGeneralAccessMessages = defineMessages({
73
74
  workspaceTitle: { id: "shareDialog.olp.generalAccess.workspace.title" },
74
75
  workspaceDescription: { id: "shareDialog.olp.generalAccess.workspace.description" },
75
76
  });
77
+ export const olpLabelMessages = defineMessages({
78
+ suffixPrimary: { id: "shareDialog.olp.label.suffix.primary" },
79
+ suffixDefault: { id: "shareDialog.olp.label.suffix.default" },
80
+ listHeading: { id: "shareDialog.olp.labels.heading" },
81
+ popoverTitle: { id: "shareDialog.olp.labels.popoverTitle" },
82
+ });
83
+ export const olpPermissionMessages = defineMessages({
84
+ canViewAndShare: { id: "shareDialog.share.granular.grantee.permission.share" },
85
+ canView: { id: "shareDialog.share.granular.grantee.permission.view" },
86
+ canViewAndShareTooltip: { id: "shareDialog.olp.permission.tooltip.share" },
87
+ canViewTooltip: { id: "shareDialog.olp.permission.tooltip.view" },
88
+ transferOwnership: { id: "shareDialog.olp.permission.transferOwnership" },
89
+ labels: { id: "shareDialog.olp.permission.labels" },
90
+ removeAccess: { id: "shareDialog.olp.permission.removeAccess" },
91
+ menuLabel: { id: "shareDialog.olp.permission.menuLabel" },
92
+ moreInfoAriaLabel: { id: "shareDialog.olp.permission.moreInfoAriaLabel" },
93
+ });
94
+ export const olpGranteeControlsMessages = defineMessages({
95
+ allLabels: { id: "shareDialog.olp.granteeControls.allLabels" },
96
+ labelsCount: { id: "shareDialog.olp.granteeControls.labelsCount" },
97
+ });