@gooddata/sdk-ui-kit 11.41.0-alpha.2 → 11.41.0-alpha.4

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.
@@ -10,6 +10,12 @@ export interface IUiControlButtonProps {
10
10
  icon?: ReactNode;
11
11
  titleExtension?: ReactNode;
12
12
  subtitleExtension?: ReactNode;
13
+ /**
14
+ * `"stacked"` (default): title above subtitle, chevron beside subtitle.
15
+ * `"row"`: single full-width line, flat background, chevron pinned right.
16
+ */
17
+ layout?: "stacked" | "row";
18
+ hideChevron?: boolean;
13
19
  isOpen?: boolean;
14
20
  isDraggable?: boolean;
15
21
  isDragging?: boolean;
@@ -41,4 +47,4 @@ export interface IUiControlButtonProps {
41
47
  *
42
48
  * @internal
43
49
  */
44
- export declare function UiControlButton({ title, titleClassName, subtitle, subtitleClassName, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel }: IUiControlButtonProps): import("react/jsx-runtime").JSX.Element;
50
+ export declare function UiControlButton({ title, titleClassName, subtitle, subtitleClassName, icon, titleExtension, subtitleExtension, layout, hideChevron, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel }: IUiControlButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -21,9 +21,12 @@ const TITLE_TOOLTIP_ALIGN_POINTS = [
21
21
  *
22
22
  * @internal
23
23
  */
24
- export function UiControlButton({ title, titleClassName, subtitle, subtitleClassName, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel, }) {
24
+ export function UiControlButton({ title, titleClassName, subtitle, subtitleClassName, icon, titleExtension, subtitleExtension, layout = "stacked", hideChevron, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel, }) {
25
25
  const tooltipId = useIdPrefixed("gd-ui-kit-control-button-tooltip");
26
26
  const showDisabledTooltip = !!disabled && !!disabledTooltip;
27
+ const hasSubtitle = subtitle !== undefined || subtitleExtension !== undefined;
28
+ // Trailing colon on the label, only when a subtitle follows it.
29
+ const showLabelColon = layout === "row" && hasSubtitle;
27
30
  const onKeyDown = (event) => {
28
31
  if (!isActionKey(event)) {
29
32
  return;
@@ -36,6 +39,8 @@ export function UiControlButton({ title, titleClassName, subtitle, subtitleClass
36
39
  onClick?.();
37
40
  };
38
41
  const button = (_jsxs("div", { id: buttonId, ref: buttonRef, className: cx(b({
42
+ layout,
43
+ hideChevron: !!hideChevron,
39
44
  isOpen: !!isOpen,
40
45
  isDraggable: !!isDraggable,
41
46
  isDragging: !!isDragging,
@@ -43,7 +48,7 @@ export function UiControlButton({ title, titleClassName, subtitle, subtitleClass
43
48
  disabled: !!disabled,
44
49
  }), className), role: "button", tabIndex: 0, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-disabled": disabled, "aria-controls": isOpen ? dropdownId : undefined, "aria-describedby": showDisabledTooltip ? tooltipId : undefined, "aria-label": ariaLabel, "data-testid": dataTestId, onClick: disabled ? undefined : onClick, onKeyDown: onKeyDown, children: [icon ? _jsx("div", { className: e("icon"), children: icon }) : null, _jsxs("div", { className: e("content"), children: [
45
50
  _jsxs("div", { className: e("title-row"), children: [
46
- _jsx("div", { className: e("title"), children: _jsx(ShortenedText, { tooltipAlignPoints: TITLE_TOOLTIP_ALIGN_POINTS, className: titleClassName, children: title }) }), titleExtension] }), subtitle === undefined && subtitleExtension === undefined ? null : (_jsxs("div", { className: e("subtitle-row"), children: [typeof subtitle === "string" ? (_jsx("div", { className: e("subtitle"), children: _jsx(ShortenedText, { tooltipAlignPoints: TITLE_TOOLTIP_ALIGN_POINTS, className: subtitleClassName, children: subtitle }) })) : (subtitle), subtitleExtension] }))] })
51
+ _jsx("div", { className: e("title", { withColon: showLabelColon }), children: _jsx(ShortenedText, { tooltipAlignPoints: TITLE_TOOLTIP_ALIGN_POINTS, className: titleClassName, children: title }) }), titleExtension] }), hasSubtitle ? (_jsxs("div", { className: e("subtitle-row"), children: [typeof subtitle === "string" ? (_jsx("div", { className: e("subtitle"), children: _jsx(ShortenedText, { tooltipAlignPoints: TITLE_TOOLTIP_ALIGN_POINTS, className: subtitleClassName, children: subtitle }) })) : (subtitle), subtitleExtension] })) : null] })
47
52
  ] }));
48
53
  if (showDisabledTooltip) {
49
54
  return (_jsx(UiTooltip, { id: tooltipId, anchor: button, content: disabledTooltip, triggerBy: ["focus"], arrowPlacement: "top", showArrow: true }));
@@ -135,9 +135,13 @@ export function UiListbox({ items, dataTestId, itemDataTestId, width, maxWidth,
135
135
  type: "mouse",
136
136
  newTab: e.ctrlKey || e.metaKey || e.button === 1,
137
137
  });
138
- }, item: item, isFocused: index === focusedIndex, isSelected: item.id === selectedItemId, isCompact: isCompact }) }, item.id)) : (_jsx("li", { ref: (el) => {
138
+ }, item: item, isFocused: index === focusedIndex, isSelected: item.id === selectedItemId, isCompact: isCompact }) }, item.id)) : (
139
+ // Static items (separators, headers) are presentational; `role="presentation"`
140
+ // keeps the <li> out of the a11y tree so it isn't exposed as a `listitem`,
141
+ // which is an invalid child of `role="listbox"` (only `option`/`group` allowed).
142
+ _jsx("li", { ref: (el) => {
139
143
  itemRefs.current[index] = el;
140
- }, "data-testid": testId, children: _jsx(StaticItemComponent, { item: item }) }, item.id ?? index));
144
+ }, role: "presentation", "data-testid": testId, children: _jsx(StaticItemComponent, { item: item }) }, item.id ?? index));
141
145
  }) }) }));
142
146
  }
143
147
  const makeItemId = (listboxId, item) => item?.type === "interactive" ? `item-${listboxId}-${item.id}` : undefined;
@@ -6,6 +6,7 @@ export interface IUiTagProps {
6
6
  isReadOnly?: boolean;
7
7
  isDisabled?: boolean;
8
8
  isFocused?: boolean;
9
+ size?: "small" | "large";
9
10
  onDelete?: (tag: IUiTagDef) => void;
10
11
  onClick?: (tag: IUiTagDef) => void;
11
12
  maxWidth?: number | "none";
@@ -4,9 +4,9 @@ import { forwardRef } from "react";
4
4
  import { bem } from "../@utils/bem.js";
5
5
  import { UiTag as UiTagComponent } from "../UiTag/UiTag.js";
6
6
  const { e } = bem("gd-ui-kit-tags");
7
- export const UiTag = forwardRef(function UiTag({ tag, maxWidth, isDeletable, isDisabled, isFocused, deleteLabel, onDelete, onClick }, ref) {
7
+ export const UiTag = forwardRef(function UiTag({ tag, maxWidth, isDeletable, isDisabled, isFocused, size, deleteLabel, onDelete, onClick }, ref) {
8
8
  const canBeDeleted = isDeletable && (tag.isDeletable ?? true);
9
- return (_jsx("div", { className: e("tag", { isFocused: isFocused ?? false }), role: "listitem", style: { maxWidth }, children: _jsx(UiTagComponent, { ref: ref, tabIndex: -1, deleteTabIndex: -1, label: `${tag.label}`, isDeletable: canBeDeleted, isDisabled: isDisabled, accessibilityConfig: {
9
+ return (_jsx("div", { className: e("tag", { isFocused: isFocused ?? false }), role: "listitem", style: { maxWidth }, children: _jsx(UiTagComponent, { ref: ref, tabIndex: -1, deleteTabIndex: -1, label: `${tag.label}`, size: size, iconBefore: tag.iconBefore, isDeletable: canBeDeleted, isDisabled: isDisabled, accessibilityConfig: {
10
10
  ariaLabel: tag.label,
11
11
  deleteAriaLabel: `${deleteLabel ?? "Delete"} ${tag.label}`,
12
12
  }, onDelete: (e) => {
@@ -2,4 +2,4 @@ import { type IUiTagsProps } from "./types.js";
2
2
  /**
3
3
  * @internal
4
4
  */
5
- export declare function UiTags({ tags, tagOptions, addLabel, nameLabel, cancelLabel, closeLabel, saveLabel, noTagsLabel, moreLabel, removeLabel, creatableLabel, mode, canDeleteTags, canCreateTag, readOnly, onTagClick, onTagAdd, onTagRemove, accessibilityConfig }: IUiTagsProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function UiTags({ tags, tagOptions, addLabel, nameLabel, cancelLabel, closeLabel, saveLabel, noTagsLabel, moreLabel, removeLabel, creatableLabel, mode, size, canDeleteTags, canCreateTag, readOnly, onTagClick, onTagAdd, onTagRemove, accessibilityConfig, renderAddButton }: IUiTagsProps): import("react/jsx-runtime").JSX.Element;
@@ -17,13 +17,15 @@ const defaultAccessibilityConfig = {};
17
17
  /**
18
18
  * @internal
19
19
  */
20
- export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Name", cancelLabel = "Cancel", closeLabel = "Close", saveLabel = "Save", noTagsLabel = "No tags", moreLabel = "More tags", removeLabel = "Remove", creatableLabel = "(Create new)", mode = "single-line", canDeleteTags = true, canCreateTag = true, readOnly = false, onTagClick = () => { }, onTagAdd = () => { }, onTagRemove = () => { }, accessibilityConfig = defaultAccessibilityConfig, }) {
20
+ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Name", cancelLabel = "Cancel", closeLabel = "Close", saveLabel = "Save", noTagsLabel = "No tags", moreLabel = "More tags", removeLabel = "Remove", creatableLabel = "(Create new)", mode = "single-line", size = "small", canDeleteTags = true, canCreateTag = true, readOnly = false, onTagClick = () => { }, onTagAdd = () => { }, onTagRemove = () => { }, accessibilityConfig = defaultAccessibilityConfig, renderAddButton, }) {
21
21
  const inputRef = useRef(null);
22
22
  const [popupOpen, setPopupOpen] = useState(false);
23
23
  const popupId = useId();
24
24
  const isDeletable = canDeleteTags && !readOnly;
25
- const isAddable = canCreateTag && !readOnly;
26
- const { rootRef, showedTags, hiddenTags, tagsContainerRef, hiddenTagsContainerRef, tooltipTagsContainerRef, addButtonRef, allContainerRef, tooltipWidth, availableWidth, lastAvailableWidth, setTooltipContainer, } = useResponsiveTags(tags, mode, [canCreateTag, canDeleteTags, readOnly]);
25
+ // A custom add-button slot takes precedence over the built-in create-tag combobox.
26
+ const hasCustomAddButton = !!renderAddButton && !readOnly;
27
+ const isAddable = canCreateTag && !readOnly && !hasCustomAddButton;
28
+ const { rootRef, showedTags, hiddenTags, tagsContainerRef, hiddenTagsContainerRef, tooltipTagsContainerRef, addButtonRef, allContainerRef, tooltipWidth, availableWidth, lastAvailableWidth, setTooltipContainer, } = useResponsiveTags(tags, mode, [canCreateTag, canDeleteTags, readOnly, hasCustomAddButton]);
27
29
  const items = [...showedTags, hiddenTags];
28
30
  const { handleKeyDown, interactionState, onMoreOpen, onMoreClose, onAddOpen, onAddClose, showedFocusedIndex, hiddenFocusedIndex, tag, setTag, onTagClickHandler, onTagRemoveHandler, onTagAddHandler, } = useTagsInteractions(rootRef, tooltipTagsContainerRef, showedTags, hiddenTags, onTagClick, onTagAdd, onTagRemove);
29
31
  const comboboxOptions = useMemo(() => {
@@ -37,7 +39,7 @@ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Na
37
39
  }, [tagOptions, tags]);
38
40
  return (_jsxs("div", { ref: rootRef, tabIndex: tags.length === 0 ? -1 : 0, onKeyDown: handleKeyDown, className: b({ readOnly, mode }), "aria-label": accessibilityConfig?.ariaLabel, "aria-labelledby": accessibilityConfig?.ariaLabelledBy, "aria-describedby": accessibilityConfig?.ariaDescribedBy, role: accessibilityConfig?.role ?? "list", children: [
39
41
  _jsx("div", { className: e("shadow-container"), ref: allContainerRef, children: tags.map((tag) => {
40
- return (_jsx(UiTag, { tag: tag, isDeletable: isDeletable }, getKey(tag, isDeletable, readOnly)));
42
+ return (_jsx(UiTag, { tag: tag, isDeletable: isDeletable, size: size }, getKey(tag, isDeletable, readOnly)));
41
43
  }) }), _jsxs("div", { className: e("tags-container"), ref: tagsContainerRef, children: [items.map((tag, i) => {
42
44
  if (Array.isArray(tag)) {
43
45
  if (hiddenTags.length === 0) {
@@ -55,7 +57,7 @@ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Na
55
57
  }, tabIndex: 0, children: hiddenTags.map((tag, i) => {
56
58
  return (_jsx(UiTag, { tag: tag, deleteLabel: removeLabel, ref: (ref) => {
57
59
  interactionState.current.tags[tag.id] = ref;
58
- }, isDeletable: isDeletable, isDisabled: readOnly, isFocused: hiddenFocusedIndex === i, maxWidth: tooltipWidth, onDelete: onTagRemoveHandler, onClick: onTagClickHandler }, i));
60
+ }, isDeletable: isDeletable, isDisabled: readOnly, isFocused: hiddenFocusedIndex === i, size: size, maxWidth: tooltipWidth, onDelete: onTagRemoveHandler, onClick: onTagClickHandler }, i));
59
61
  }) })), footer: ({ onClose }) => (_jsx(_Fragment, { children: _jsx(UiButton, { ref: (ref) => {
60
62
  interactionState.current.close = ref;
61
63
  }, label: closeLabel, variant: "secondary", onClick: () => {
@@ -64,8 +66,11 @@ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Na
64
66
  }
65
67
  return (_jsx(UiTag, { tag: tag, ref: (ref) => {
66
68
  interactionState.current.tags[tag.id] = ref;
67
- }, isDeletable: isDeletable, isDisabled: readOnly, deleteLabel: removeLabel, isFocused: showedFocusedIndex === i, onDelete: onTagRemoveHandler, onClick: onTagClickHandler, maxWidth: i === showedTags.length - 1 ? lastAvailableWidth : availableWidth }, getKey(tag, isDeletable, readOnly)));
68
- }), isAddable ? (_jsx("div", { className: e("add-button"), ref: addButtonRef, onClick: (e) => {
69
+ }, isDeletable: isDeletable, isDisabled: readOnly, deleteLabel: removeLabel, isFocused: showedFocusedIndex === i, size: size, onDelete: onTagRemoveHandler, onClick: onTagClickHandler, maxWidth: i === showedTags.length - 1 ? lastAvailableWidth : availableWidth }, getKey(tag, isDeletable, readOnly)));
70
+ }), hasCustomAddButton ? (_jsx("div", { className: e("add-button"), ref: addButtonRef, onClick: (e) => {
71
+ e.stopPropagation();
72
+ e.preventDefault();
73
+ }, children: renderAddButton?.() })) : isAddable ? (_jsx("div", { className: e("add-button"), ref: addButtonRef, onClick: (e) => {
69
74
  e.stopPropagation();
70
75
  e.preventDefault();
71
76
  }, children: _jsx(UiPopover, { id: popupId, onOpen: () => {
@@ -106,7 +111,7 @@ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Na
106
111
  }, ref: (ref) => {
107
112
  interactionState.current.save = ref;
108
113
  } })
109
- ] })) }) })) : null, !isAddable && tags.length === 0 ? (_jsx("div", { className: e("empty-state"), children: noTagsLabel })) : null] })
114
+ ] })) }) })) : null, !isAddable && !hasCustomAddButton && tags.length === 0 ? (_jsx("div", { className: e("empty-state"), children: noTagsLabel })) : null] })
110
115
  ] }));
111
116
  }
112
117
  function getKey(tag, isDeletable, readOnly) {
@@ -1,4 +1,6 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type IAccessibilityConfigBase } from "../../typings/accessibility.js";
3
+ import { type IconType } from "../@types/icon.js";
2
4
  /**
3
5
  * @internal
4
6
  */
@@ -18,10 +20,20 @@ export interface IUiTagsProps {
18
20
  canCreateTag?: boolean;
19
21
  canDeleteTags?: boolean;
20
22
  mode?: "single-line" | "multi-line";
23
+ /** Visual size of the rendered tags. Defaults to "small". */
24
+ size?: "small" | "large";
21
25
  onTagClick?: (tag: IUiTagDef) => void;
22
26
  onTagAdd?: (tag: IUiTagDef) => void;
23
27
  onTagRemove?: (tag: IUiTagDef) => void;
24
28
  accessibilityConfig?: IAccessibilityConfigBase;
29
+ /**
30
+ * Renders custom content in the add-button slot — the last item in the tags row,
31
+ * in-flow with the tags so it sits right after them (and the "+N" overflow chip)
32
+ * and is included in the responsive overflow measurement. When provided, it
33
+ * replaces the built-in create-tag combobox; use it to host a bespoke picker
34
+ * while keeping the tag display and overflow behaviour. Ignored when readOnly.
35
+ */
36
+ renderAddButton?: () => ReactNode;
25
37
  }
26
38
  /**
27
39
  * @internal
@@ -30,4 +42,5 @@ export interface IUiTagDef {
30
42
  id: string;
31
43
  label: string;
32
44
  isDeletable?: boolean;
45
+ iconBefore?: IconType;
33
46
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // (C) 2007-2025 GoodData Corporation
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ // (C) 2007-2026 GoodData Corporation
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { throttle } from "lodash-es";
5
5
  import { elementRegion } from "../utils/domUtilities.js";
6
6
  /**
@@ -23,10 +23,31 @@ export function AutoSize({ children }) {
23
23
  });
24
24
  }, []);
25
25
  const throttledUpdateSize = useMemo(() => throttle(updateSize, 250, { leading: false }), [updateSize]);
26
+ // Measure synchronously before paint so the first committed frame already has a real width
27
+ // instead of 0 (which made children shrink-wrap). This handles the initial render.
28
+ useLayoutEffect(() => {
29
+ updateSize();
30
+ }, [updateSize]);
26
31
  useEffect(() => {
27
- window.addEventListener("resize", throttledUpdateSize);
28
- throttledUpdateSize();
32
+ const node = wrapperRef.current;
33
+ // Re-measure whenever the wrapper actually resizes. This catches the post-mount layout
34
+ // settle (e.g. a fullscreen overlay finishing its alignment a few frames later, where the
35
+ // synchronous measure above read a not-yet-final width - such as before a scrollbar
36
+ // disappears) as well as any later container or window resize - precisely, instead of
37
+ // guessing with a fixed timeout. Throttled to coalesce bursts during continuous resizing.
38
+ let observer;
39
+ if (node && typeof ResizeObserver !== "undefined") {
40
+ observer = new ResizeObserver(() => {
41
+ throttledUpdateSize();
42
+ });
43
+ observer.observe(node);
44
+ }
45
+ else {
46
+ // Fallback for environments without ResizeObserver.
47
+ window.addEventListener("resize", throttledUpdateSize);
48
+ }
29
49
  return () => {
50
+ observer?.disconnect();
30
51
  throttledUpdateSize.cancel();
31
52
  window.removeEventListener("resize", throttledUpdateSize);
32
53
  };
@@ -0,0 +1,26 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * @internal
4
+ */
5
+ export type WidgetNoticeType = "info" | "success" | "warning" | "error";
6
+ /**
7
+ * @internal
8
+ */
9
+ export interface IWidgetNoticeProps {
10
+ type?: WidgetNoticeType;
11
+ message: ReactNode;
12
+ action?: ReactNode;
13
+ detail?: ReactNode;
14
+ detailAction?: ReactNode;
15
+ expandLabel?: ReactNode;
16
+ collapseLabel?: ReactNode;
17
+ defaultExpanded?: boolean;
18
+ showIcon?: boolean;
19
+ onClose?: () => void;
20
+ closeButtonLabel?: string;
21
+ dataTestId?: string;
22
+ }
23
+ /**
24
+ * @internal
25
+ */
26
+ export declare function WidgetNotice({ type, message, action, detail, detailAction, expandLabel, collapseLabel, defaultExpanded, showIcon, onClose, closeButtonLabel, dataTestId }: IWidgetNoticeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useId, useState } from "react";
4
+ import { useIntl } from "react-intl";
5
+ import { bem } from "../@ui/@utils/bem.js";
6
+ import { UiIcon } from "../@ui/UiIcon/UiIcon.js";
7
+ import { UiIconButton } from "../@ui/UiIconButton/UiIconButton.js";
8
+ import { commonDialogMessages } from "../locales.js";
9
+ const { b, e } = bem("gd-ui-kit-widget-notice");
10
+ const ICON_BY_TYPE = {
11
+ info: "infoCircle",
12
+ success: "checkCircle",
13
+ warning: "warning",
14
+ error: "exclamationCircle",
15
+ };
16
+ const ICON_COLOR_BY_TYPE = {
17
+ info: "primary",
18
+ success: "success",
19
+ warning: "warning",
20
+ error: "error",
21
+ };
22
+ /**
23
+ * @internal
24
+ */
25
+ export function WidgetNotice({ type = "info", message, action, detail, detailAction, expandLabel, collapseLabel, defaultExpanded = false, showIcon = true, onClose, closeButtonLabel, dataTestId, }) {
26
+ const intl = useIntl();
27
+ const detailId = useId();
28
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
29
+ const hasDetail = Boolean(detail);
30
+ const effectiveCloseButtonLabel = closeButtonLabel ?? intl.formatMessage(commonDialogMessages.close);
31
+ return (_jsxs("div", { className: b({ type, expandable: hasDetail }), "data-testid": dataTestId, children: [
32
+ _jsxs("div", { className: e("header"), children: [
33
+ _jsxs("div", { className: e("content"), children: [showIcon ? (_jsx("span", { className: e("icon"), "aria-hidden": "true", children: _jsx(UiIcon, { type: ICON_BY_TYPE[type], size: 16, color: ICON_COLOR_BY_TYPE[type] }) })) : null, _jsxs("div", { className: e("text"), children: [
34
+ _jsx("span", { className: e("message"), children: message }), action ? _jsx("span", { className: e("action"), children: action }) : null, hasDetail ? (_jsx("button", { type: "button", className: e("toggle"), "aria-expanded": isExpanded, "aria-controls": isExpanded ? detailId : undefined, onClick: () => setIsExpanded((wasExpanded) => !wasExpanded), children: isExpanded ? collapseLabel : expandLabel })) : null] })
35
+ ] }), onClose ? (_jsx(UiIconButton, { icon: "cross", variant: "tertiary", size: "xsmall", label: effectiveCloseButtonLabel, onClick: onClose, dataTestId: "widget-notice-close" })) : null] }), hasDetail && isExpanded ? (_jsxs("div", { id: detailId, className: e("detail"), children: [
36
+ _jsx("div", { className: e("detail-text"), children: detail }), detailAction ? _jsx("div", { className: e("detail-action"), children: detailAction }) : null] })) : null] }));
37
+ }
package/esm/index.d.ts CHANGED
@@ -95,6 +95,7 @@ export { ToastMessageList } from "./Messages/toasts/ToastsCenterMessage.js";
95
95
  export { ToastsCenter, ToastsCenterContextProvider, ScreenReaderToast, } from "./Messages/toasts/ToastsCenter.js";
96
96
  export { NonContextToastsInterop } from "./Messages/toasts/NonContextToastsInterop.js";
97
97
  export { ToastsCenterContext, useToastsCenterValue, type IToastsCenterContext, } from "./Messages/toasts/context.js";
98
+ export { WidgetNotice, type IWidgetNoticeProps, type WidgetNoticeType } from "./WidgetNotice/WidgetNotice.js";
98
99
  export type { IDialogBaseProps, IDialogProps, IConfirmDialogBaseProps, IExportDialogProps, IExportDialogBaseProps, IDialogCloseButtonProps, IExportDialogData, PageOrientation, PageSize, IExportTabularPdfDialogData, IExportTabularPdfDialogProps, } from "./Dialog/typings.js";
99
100
  export { BackButton, type IBackButtonProps } from "./Dialog/BackButton.js";
100
101
  export { ConfirmDialog } from "./Dialog/ConfirmDialog.js";
package/esm/index.js CHANGED
@@ -87,6 +87,7 @@ export { ToastMessageList } from "./Messages/toasts/ToastsCenterMessage.js";
87
87
  export { ToastsCenter, ToastsCenterContextProvider, ScreenReaderToast, } from "./Messages/toasts/ToastsCenter.js";
88
88
  export { NonContextToastsInterop } from "./Messages/toasts/NonContextToastsInterop.js";
89
89
  export { ToastsCenterContext, useToastsCenterValue, } from "./Messages/toasts/context.js";
90
+ export { WidgetNotice } from "./WidgetNotice/WidgetNotice.js";
90
91
  export { BackButton } from "./Dialog/BackButton.js";
91
92
  export { ConfirmDialog } from "./Dialog/ConfirmDialog.js";
92
93
  export { ConfirmDialogBase } from "./Dialog/ConfirmDialogBase.js";
@@ -6767,6 +6767,12 @@ export declare interface IUiControlButtonProps {
6767
6767
  icon?: ReactNode;
6768
6768
  titleExtension?: ReactNode;
6769
6769
  subtitleExtension?: ReactNode;
6770
+ /**
6771
+ * `"stacked"` (default): title above subtitle, chevron beside subtitle.
6772
+ * `"row"`: single full-width line, flat background, chevron pinned right.
6773
+ */
6774
+ layout?: "stacked" | "row";
6775
+ hideChevron?: boolean;
6770
6776
  isOpen?: boolean;
6771
6777
  isDraggable?: boolean;
6772
6778
  isDragging?: boolean;
@@ -8347,6 +8353,7 @@ export declare interface IUiTagDef {
8347
8353
  id: string;
8348
8354
  label: string;
8349
8355
  isDeletable?: boolean;
8356
+ iconBefore?: IconType;
8350
8357
  }
8351
8358
 
8352
8359
  /**
@@ -8368,10 +8375,20 @@ export declare interface IUiTagsProps {
8368
8375
  canCreateTag?: boolean;
8369
8376
  canDeleteTags?: boolean;
8370
8377
  mode?: "single-line" | "multi-line";
8378
+ /** Visual size of the rendered tags. Defaults to "small". */
8379
+ size?: "small" | "large";
8371
8380
  onTagClick?: (tag: IUiTagDef) => void;
8372
8381
  onTagAdd?: (tag: IUiTagDef) => void;
8373
8382
  onTagRemove?: (tag: IUiTagDef) => void;
8374
8383
  accessibilityConfig?: IAccessibilityConfigBase;
8384
+ /**
8385
+ * Renders custom content in the add-button slot — the last item in the tags row,
8386
+ * in-flow with the tags so it sits right after them (and the "+N" overflow chip)
8387
+ * and is included in the responsive overflow measurement. When provided, it
8388
+ * replaces the built-in create-tag combobox; use it to host a bespoke picker
8389
+ * while keeping the tag display and overflow behaviour. Ignored when readOnly.
8390
+ */
8391
+ renderAddButton?: () => ReactNode;
8375
8392
  }
8376
8393
 
8377
8394
  /**
@@ -8864,6 +8881,24 @@ export declare interface IWebComponentsOptions {
8864
8881
  unit?: UnitsType;
8865
8882
  }
8866
8883
 
8884
+ /**
8885
+ * @internal
8886
+ */
8887
+ export declare interface IWidgetNoticeProps {
8888
+ type?: WidgetNoticeType;
8889
+ message: ReactNode;
8890
+ action?: ReactNode;
8891
+ detail?: ReactNode;
8892
+ detailAction?: ReactNode;
8893
+ expandLabel?: ReactNode;
8894
+ collapseLabel?: ReactNode;
8895
+ defaultExpanded?: boolean;
8896
+ showIcon?: boolean;
8897
+ onClose?: () => void;
8898
+ closeButtonLabel?: string;
8899
+ dataTestId?: string;
8900
+ }
8901
+
8867
8902
  /**
8868
8903
  * @internal
8869
8904
  */
@@ -10156,7 +10191,7 @@ export declare function UiConfirmDialog({ title, description, confirmLabel, conf
10156
10191
  *
10157
10192
  * @internal
10158
10193
  */
10159
- export declare function UiControlButton({ title, titleClassName, subtitle, subtitleClassName, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel }: IUiControlButtonProps): JSX.Element;
10194
+ export declare function UiControlButton({ title, titleClassName, subtitle, subtitleClassName, icon, titleExtension, subtitleExtension, layout, hideChevron, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel }: IUiControlButtonProps): JSX.Element;
10160
10195
 
10161
10196
  /**
10162
10197
  * @internal
@@ -10592,7 +10627,7 @@ export declare type UiTagProps = {
10592
10627
  /**
10593
10628
  * @internal
10594
10629
  */
10595
- export declare function UiTags({ tags, tagOptions, addLabel, nameLabel, cancelLabel, closeLabel, saveLabel, noTagsLabel, moreLabel, removeLabel, creatableLabel, mode, canDeleteTags, canCreateTag, readOnly, onTagClick, onTagAdd, onTagRemove, accessibilityConfig }: IUiTagsProps): JSX.Element;
10630
+ export declare function UiTags({ tags, tagOptions, addLabel, nameLabel, cancelLabel, closeLabel, saveLabel, noTagsLabel, moreLabel, removeLabel, creatableLabel, mode, size, canDeleteTags, canCreateTag, readOnly, onTagClick, onTagAdd, onTagRemove, accessibilityConfig, renderAddButton }: IUiTagsProps): JSX.Element;
10596
10631
 
10597
10632
  /**
10598
10633
  * Single-line text input with optional label and leading / trailing icons.
@@ -11239,6 +11274,16 @@ export declare type VariantTooltip = "tooltip";
11239
11274
  */
11240
11275
  export declare type VerticalPosition = "top" | "center" | "bottom";
11241
11276
 
11277
+ /**
11278
+ * @internal
11279
+ */
11280
+ export declare function WidgetNotice({ type, message, action, detail, detailAction, expandLabel, collapseLabel, defaultExpanded, showIcon, onClose, closeButtonLabel, dataTestId }: IWidgetNoticeProps): JSX.Element;
11281
+
11282
+ /**
11283
+ * @internal
11284
+ */
11285
+ export declare type WidgetNoticeType = "info" | "success" | "warning" | "error";
11286
+
11242
11287
  /**
11243
11288
  * @internal
11244
11289
  */
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.55.2"
8
+ "packageVersion": "7.58.7"
9
9
  }
10
10
  ]
11
11
  }
@@ -1,3 +1,4 @@
1
+ import { type DebouncedFunc } from "lodash-es";
1
2
  /**
2
3
  * Custom event name for goodstrap drag events.
3
4
  * Used by Overlay to handle closeOnMouseDrag and other drag-related behaviors.
@@ -9,4 +10,4 @@ export declare const GOODSTRAP_DRAG_EVENT = "goodstrap.drag";
9
10
  * This event is throttled by default
10
11
  * @internal
11
12
  */
12
- export declare const handleOnGoodstrapDragEvent: import("lodash-es").DebouncedFunc<() => void>;
13
+ export declare const handleOnGoodstrapDragEvent: DebouncedFunc<() => void>;
package/esm/utils/drag.js CHANGED
@@ -1,4 +1,4 @@
1
- // (C) 2022-2025 GoodData Corporation
1
+ // (C) 2022-2026 GoodData Corporation
2
2
  import { throttle } from "lodash-es";
3
3
  /**
4
4
  * Custom event name for goodstrap drag events.
@@ -1,3 +1,4 @@
1
+ import { type DebouncedFunc } from "lodash-es";
1
2
  /**
2
3
  * Custom event name for goodstrap scroll events.
3
4
  * Used by Overlay to handle closeOnParentScroll and other scroll-related behaviors.
@@ -9,4 +10,4 @@ export declare const GOODSTRAP_SCROLLED_EVENT = "goodstrap.scrolled";
9
10
  * This event is throttled by default
10
11
  * @internal
11
12
  */
12
- export declare const handleOnScrollEvent: import("lodash-es").DebouncedFunc<(node: HTMLElement) => void>;
13
+ export declare const handleOnScrollEvent: DebouncedFunc<(node: HTMLElement) => void>;
@@ -1,4 +1,4 @@
1
- // (C) 2022-2025 GoodData Corporation
1
+ // (C) 2022-2026 GoodData Corporation
2
2
  import { throttle } from "lodash-es";
3
3
  /**
4
4
  * Custom event name for goodstrap scroll events.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-kit",
3
- "version": "11.41.0-alpha.2",
3
+ "version": "11.41.0-alpha.4",
4
4
  "description": "GoodData SDK - UI Building Components",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -74,12 +74,12 @@
74
74
  "ts-invariant": "0.10.3",
75
75
  "tslib": "2.8.1",
76
76
  "unified": "^11.0.5",
77
- "uuid": "11.1.0",
78
- "@gooddata/sdk-backend-spi": "11.41.0-alpha.2",
79
- "@gooddata/sdk-model": "11.41.0-alpha.2",
80
- "@gooddata/sdk-ui": "11.41.0-alpha.2",
81
- "@gooddata/sdk-ui-theme-provider": "11.41.0-alpha.2",
82
- "@gooddata/util": "11.41.0-alpha.2"
77
+ "uuid": "11.1.1",
78
+ "@gooddata/sdk-backend-spi": "11.41.0-alpha.4",
79
+ "@gooddata/sdk-ui": "11.41.0-alpha.4",
80
+ "@gooddata/sdk-model": "11.41.0-alpha.4",
81
+ "@gooddata/util": "11.41.0-alpha.4",
82
+ "@gooddata/sdk-ui-theme-provider": "11.41.0-alpha.4"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@microsoft/api-documenter": "^7.17.0",
@@ -128,11 +128,11 @@
128
128
  "typescript": "5.9.3",
129
129
  "vitest": "4.1.8",
130
130
  "vitest-dom": "0.1.1",
131
- "@gooddata/eslint-config": "11.41.0-alpha.2",
132
- "@gooddata/oxlint-config": "11.41.0-alpha.2",
133
- "@gooddata/reference-workspace": "11.41.0-alpha.2",
134
- "@gooddata/sdk-backend-mockingbird": "11.41.0-alpha.2",
135
- "@gooddata/stylelint-config": "11.41.0-alpha.2"
131
+ "@gooddata/eslint-config": "11.41.0-alpha.4",
132
+ "@gooddata/oxlint-config": "11.41.0-alpha.4",
133
+ "@gooddata/reference-workspace": "11.41.0-alpha.4",
134
+ "@gooddata/sdk-backend-mockingbird": "11.41.0-alpha.4",
135
+ "@gooddata/stylelint-config": "11.41.0-alpha.4"
136
136
  },
137
137
  "peerDependencies": {
138
138
  "react": "^18.0.0 || ^19.0.0",
@@ -115,6 +115,73 @@ $drag-handle-icon: "data:image/svg+xml,%3Csvg width='7' height='26' viewBox='0 0
115
115
  }
116
116
  }
117
117
 
118
+ // Single full-width line, flat (no border/gradient), chevron pinned right.
119
+ // Declared after the state modifiers so its flat `::after` wins.
120
+ &--layout-row {
121
+ width: 100%;
122
+ max-width: none;
123
+ box-sizing: border-box;
124
+ font-size: 14px;
125
+ border-radius: 0;
126
+ padding-top: 15px;
127
+ padding-bottom: 15px;
128
+ // Room for the pinned chevron
129
+ padding-right: 28px;
130
+
131
+ // Flat: no chip border or open-state gradient.
132
+ &::after {
133
+ display: none;
134
+ }
135
+
136
+ #{$root}__icon {
137
+ display: flex;
138
+ height: 100%;
139
+ align-items: center;
140
+ }
141
+
142
+ // One left-aligned line; chevron is taken out of flow.
143
+ #{$root}__content {
144
+ flex-direction: row;
145
+ align-items: center;
146
+ justify-content: flex-start;
147
+ }
148
+
149
+ // min-width: 0 lets a long label ellipsise instead of overflowing.
150
+ #{$root}__title-row {
151
+ flex: 0 1 auto;
152
+ min-width: 0;
153
+ max-width: 100%;
154
+ margin-right: 5px;
155
+ }
156
+
157
+ #{$root}__title--withColon::after {
158
+ content: ":";
159
+ }
160
+
161
+ #{$root}__subtitle-row {
162
+ flex: 1 1 auto;
163
+ min-width: 0;
164
+ max-width: none;
165
+
166
+ // Chevron
167
+ &::after {
168
+ position: absolute;
169
+ top: 50%;
170
+ // `content` is inset by padding-right lands it 12px from the button edge.
171
+ right: -16px;
172
+ margin-left: 0;
173
+ font-size: 18px;
174
+ transform: translateY(-50%);
175
+ }
176
+ }
177
+ }
178
+
179
+ &--hideChevron {
180
+ #{$root}__subtitle-row::after {
181
+ display: none;
182
+ }
183
+ }
184
+
118
185
  &__icon {
119
186
  position: relative;
120
187
  z-index: 1;
@@ -4,6 +4,7 @@
4
4
 
5
5
  $tags_line_height: 20px;
6
6
  $tags_tooltip_max_width: 280px;
7
+ $tags_tooltip_viewport_margin: 160px;
7
8
 
8
9
  @mixin tag($margin-right: 5px) {
9
10
  .gd-ui-kit-tags__tag {
@@ -135,6 +136,10 @@ $tags_tooltip_max_width: 280px;
135
136
  display: flex;
136
137
  max-width: $tags_tooltip_max_width;
137
138
  width: $tags_tooltip_max_width;
139
+ max-height: calc(100vh - #{$tags_tooltip_viewport_margin});
140
+ overflow-x: hidden;
141
+ overflow-y: auto;
142
+ overscroll-behavior: contain;
138
143
  flex-direction: row;
139
144
  flex-wrap: wrap;
140
145
  gap: 5px;