@gooddata/sdk-ui-kit 11.40.0-alpha.3 → 11.40.0-alpha.5
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/esm/@ui/UiToast/UiToastProvider.d.ts +51 -0
- package/esm/@ui/UiToast/UiToastProvider.js +193 -0
- package/esm/@ui/UiToast/UiToastsContainer.d.ts +33 -0
- package/esm/@ui/UiToast/UiToastsContainer.js +56 -0
- package/esm/@ui/UiToast/types.d.ts +125 -0
- package/esm/@ui/UiToast/types.js +2 -0
- package/esm/FlexDimensions/FlexDimensions.d.ts +1 -0
- package/esm/FlexDimensions/FlexDimensions.js +6 -2
- package/esm/Header/generateHeaderMenuItemsGroups.js +5 -2
- package/esm/ShortenedText/ShortenedText.js +7 -5
- package/esm/index.d.ts +3 -0
- package/esm/index.js +2 -0
- package/esm/sdk-ui-kit.d.ts +194 -0
- package/package.json +15 -15
- package/src/@ui/UiToast/UiToast.scss +114 -0
- package/src/@ui/index.scss +1 -0
- package/styles/css/main.css +109 -0
- package/styles/css/main.css.map +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type IUiToast, type IUiToastOptions, type IUseUiToastResult, type UiToastKind } from "./types.js";
|
|
3
|
+
interface IUiToastContextValue {
|
|
4
|
+
/** Currently shown toasts, oldest first. */
|
|
5
|
+
toasts: IUiToast[];
|
|
6
|
+
/**
|
|
7
|
+
* Push a toast onto the queue and return its id. `text` is already
|
|
8
|
+
* resolved by the consumer — see [[useUiToast]] for the wrapper that
|
|
9
|
+
* runs `resolveText` against the consumer's `useIntl()`.
|
|
10
|
+
*/
|
|
11
|
+
push: (kind: UiToastKind, text: string, options?: IUiToastOptions) => string;
|
|
12
|
+
/** Dismiss one toast by id. */
|
|
13
|
+
remove: (id: string) => void;
|
|
14
|
+
/** Dismiss all toasts. */
|
|
15
|
+
removeAll: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Whether this provider is nested inside another `UiToastProvider`. The
|
|
18
|
+
* outermost provider renders the visible container; nested providers
|
|
19
|
+
* forward through context so the topmost-only behaviour is preserved.
|
|
20
|
+
*/
|
|
21
|
+
isNested: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Mount once near the root of your app. Provides the toast queue + dismiss
|
|
25
|
+
* timers consumed by `useUiToast` and `UiToastsContainer`. Nesting is safe
|
|
26
|
+
* — only the outermost `<UiToastsContainer>` renders any visible toasts.
|
|
27
|
+
*
|
|
28
|
+
* Known limitation: if a root provider with in-flight toasts is **re-parented**
|
|
29
|
+
* under a new outer provider mid-flight (e.g. the app rearranges its tree),
|
|
30
|
+
* the inner instance's queue and timers don't migrate to the parent. Mount
|
|
31
|
+
* the provider once at the stable app root to avoid this.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function UiToastProvider({ children }: {
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
/**
|
|
39
|
+
* Consumer hook. Resolves the toast message via the **caller's**
|
|
40
|
+
* `useIntl()` so a consumer rendered under a nested `IntlProvider` formats
|
|
41
|
+
* `MessageDescriptor` content with that subtree's locale, not the
|
|
42
|
+
* provider's. Throws when called outside a `UiToastProvider`.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare function useUiToast(): IUseUiToastResult;
|
|
47
|
+
/**
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export declare const UiToastContextInternal: import("react").Context<IUiToastContextValue | null>;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
const DEFAULT_DURATION_MS = 4000;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a `UiToastMessage` to a flat string using the supplied `intl`.
|
|
8
|
+
* `UiToastIntlValues` only accepts `PrimitiveType` interpolation values, so
|
|
9
|
+
* `formatMessage` always returns `string` — no `ReactNode[]` coercion needed.
|
|
10
|
+
*
|
|
11
|
+
* Called from `useUiToast` so resolution uses the **consumer's** `useIntl()`
|
|
12
|
+
* (the locale of the calling subtree) rather than the provider's — a
|
|
13
|
+
* provider mounted at the app root would otherwise format with the root
|
|
14
|
+
* locale even when the caller sits under a nested `IntlProvider`.
|
|
15
|
+
*/
|
|
16
|
+
const resolveText = (intl, message, options) => {
|
|
17
|
+
if (typeof message === "string") {
|
|
18
|
+
return options?.values
|
|
19
|
+
? intl.formatMessage({ id: message, defaultMessage: message }, options.values)
|
|
20
|
+
: message;
|
|
21
|
+
}
|
|
22
|
+
return intl.formatMessage(message.descriptor, message.values);
|
|
23
|
+
};
|
|
24
|
+
const UiToastContext = createContext(null);
|
|
25
|
+
/**
|
|
26
|
+
* Mount once near the root of your app. Provides the toast queue + dismiss
|
|
27
|
+
* timers consumed by `useUiToast` and `UiToastsContainer`. Nesting is safe
|
|
28
|
+
* — only the outermost `<UiToastsContainer>` renders any visible toasts.
|
|
29
|
+
*
|
|
30
|
+
* Known limitation: if a root provider with in-flight toasts is **re-parented**
|
|
31
|
+
* under a new outer provider mid-flight (e.g. the app rearranges its tree),
|
|
32
|
+
* the inner instance's queue and timers don't migrate to the parent. Mount
|
|
33
|
+
* the provider once at the stable app root to avoid this.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function UiToastProvider({ children }) {
|
|
38
|
+
const parent = useContext(UiToastContext);
|
|
39
|
+
const [toasts, setToasts] = useState([]);
|
|
40
|
+
// `toastsRef` is the synchronous source of truth for the queue —
|
|
41
|
+
// mutators (`push` / `remove` / `removeAll`) update it FIRST and only
|
|
42
|
+
// then schedule the `setToasts` render. That way a same-tick
|
|
43
|
+
// `add → remove` sequence sees the just-added toast in the ref, and
|
|
44
|
+
// side effects (timers, `onDismiss`) run consistently without depending
|
|
45
|
+
// on React's commit timing. React state mirrors the ref on every render.
|
|
46
|
+
const toastsRef = useRef(toasts);
|
|
47
|
+
const timersRef = useRef(new Map());
|
|
48
|
+
const dismissedRef = useRef(new Set());
|
|
49
|
+
const idPrefix = useId();
|
|
50
|
+
const counterRef = useRef(0);
|
|
51
|
+
const clearTimer = useCallback((id) => {
|
|
52
|
+
const timer = timersRef.current.get(id);
|
|
53
|
+
if (timer) {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
timersRef.current.delete(id);
|
|
56
|
+
}
|
|
57
|
+
}, []);
|
|
58
|
+
// Single mutation point — update the ref synchronously, then sync the
|
|
59
|
+
// React state via the same value. Other mutators call this so the ref
|
|
60
|
+
// and the state stay in lockstep without depending on commit timing.
|
|
61
|
+
const commit = useCallback((next) => {
|
|
62
|
+
toastsRef.current = next;
|
|
63
|
+
setToasts(next);
|
|
64
|
+
}, []);
|
|
65
|
+
const remove = useCallback((id) => {
|
|
66
|
+
// Bail if this id has already been dismissed (auto-dismiss +
|
|
67
|
+
// manual race, or StrictMode's double-invocation). Otherwise
|
|
68
|
+
// tear down synchronously: ref + state in the same tick, side
|
|
69
|
+
// effects (timer cleanup, `onDismiss`) right after.
|
|
70
|
+
if (dismissedRef.current.has(id))
|
|
71
|
+
return;
|
|
72
|
+
const t = toastsRef.current.find((x) => x.id === id);
|
|
73
|
+
if (!t)
|
|
74
|
+
return;
|
|
75
|
+
dismissedRef.current.add(id);
|
|
76
|
+
clearTimer(id);
|
|
77
|
+
commit(toastsRef.current.filter((x) => x.id !== id));
|
|
78
|
+
t.onDismiss?.();
|
|
79
|
+
}, [clearTimer, commit]);
|
|
80
|
+
const removeAll = useCallback(() => {
|
|
81
|
+
// Snapshot first so the loop sees a stable list even if `onDismiss`
|
|
82
|
+
// turns around and calls `add` / `remove` synchronously. Ref +
|
|
83
|
+
// state are cleared together; side effects run after the commit
|
|
84
|
+
// call, so a throwing `onDismiss` can't corrupt the queue.
|
|
85
|
+
const snapshot = toastsRef.current;
|
|
86
|
+
const onDismisses = [];
|
|
87
|
+
for (const t of snapshot) {
|
|
88
|
+
if (!dismissedRef.current.has(t.id)) {
|
|
89
|
+
dismissedRef.current.add(t.id);
|
|
90
|
+
if (t.onDismiss)
|
|
91
|
+
onDismisses.push(t.onDismiss);
|
|
92
|
+
}
|
|
93
|
+
clearTimer(t.id);
|
|
94
|
+
}
|
|
95
|
+
commit([]);
|
|
96
|
+
for (const cb of onDismisses)
|
|
97
|
+
cb();
|
|
98
|
+
}, [clearTimer, commit]);
|
|
99
|
+
const push = useCallback((kind, text, options) => {
|
|
100
|
+
const id = options?.id ?? `${idPrefix}-${counterRef.current++}`;
|
|
101
|
+
const sticky = options?.sticky ?? false;
|
|
102
|
+
const durationMs = options?.durationMs ?? DEFAULT_DURATION_MS;
|
|
103
|
+
const toast = {
|
|
104
|
+
id,
|
|
105
|
+
kind,
|
|
106
|
+
text,
|
|
107
|
+
sticky,
|
|
108
|
+
durationMs,
|
|
109
|
+
action: options?.action,
|
|
110
|
+
accessibilityConfig: options?.accessibilityConfig,
|
|
111
|
+
onDismiss: options?.onDismiss,
|
|
112
|
+
};
|
|
113
|
+
// Reusing an existing id replaces the toast in place and resets
|
|
114
|
+
// its dismiss timer.
|
|
115
|
+
clearTimer(id);
|
|
116
|
+
dismissedRef.current.delete(id);
|
|
117
|
+
const prev = toastsRef.current;
|
|
118
|
+
const existing = prev.findIndex((t) => t.id === id);
|
|
119
|
+
const next = existing >= 0 ? prev.map((t, i) => (i === existing ? toast : t)) : [...prev, toast];
|
|
120
|
+
commit(next);
|
|
121
|
+
if (!sticky) {
|
|
122
|
+
const timer = setTimeout(() => remove(id), durationMs);
|
|
123
|
+
timersRef.current.set(id, timer);
|
|
124
|
+
}
|
|
125
|
+
return id;
|
|
126
|
+
}, [idPrefix, remove, clearTimer, commit]);
|
|
127
|
+
// Cancel pending dismiss timers on unmount.
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const timers = timersRef.current;
|
|
130
|
+
return () => {
|
|
131
|
+
for (const timer of timers.values())
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
timers.clear();
|
|
134
|
+
};
|
|
135
|
+
}, []);
|
|
136
|
+
// Trim `dismissedRef` to ids still in `toasts` once React commits a
|
|
137
|
+
// removal. The ref's only job is to dedupe within a single dispatch
|
|
138
|
+
// (StrictMode double-invocation, auto-dismiss + manual race) — once
|
|
139
|
+
// the toast has left state, no future call can race on the same id,
|
|
140
|
+
// so the ref entry is dead weight. Without this trim the Set would
|
|
141
|
+
// grow monotonically for every auto-generated id in a long-running
|
|
142
|
+
// SPA. Runs after commit, so StrictMode's pre-commit second pass
|
|
143
|
+
// still sees the id during the dispatch that removed it.
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const liveIds = new Set(toasts.map((t) => t.id));
|
|
146
|
+
for (const id of dismissedRef.current) {
|
|
147
|
+
if (!liveIds.has(id))
|
|
148
|
+
dismissedRef.current.delete(id);
|
|
149
|
+
}
|
|
150
|
+
}, [toasts]);
|
|
151
|
+
const value = useMemo(() => {
|
|
152
|
+
if (parent) {
|
|
153
|
+
// Nested provider: forward to the parent so a single queue is the
|
|
154
|
+
// source of truth. `isNested` flips so any nested
|
|
155
|
+
// `UiToastsContainer` knows to render nothing.
|
|
156
|
+
return { ...parent, isNested: true };
|
|
157
|
+
}
|
|
158
|
+
return { toasts, push, remove, removeAll, isNested: false };
|
|
159
|
+
}, [parent, toasts, push, remove, removeAll]);
|
|
160
|
+
return _jsx(UiToastContext.Provider, { value: value, children: children });
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Consumer hook. Resolves the toast message via the **caller's**
|
|
164
|
+
* `useIntl()` so a consumer rendered under a nested `IntlProvider` formats
|
|
165
|
+
* `MessageDescriptor` content with that subtree's locale, not the
|
|
166
|
+
* provider's. Throws when called outside a `UiToastProvider`.
|
|
167
|
+
*
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
export function useUiToast() {
|
|
171
|
+
const ctx = useContext(UiToastContext);
|
|
172
|
+
if (!ctx) {
|
|
173
|
+
throw new Error("useUiToast must be used inside <UiToastProvider>");
|
|
174
|
+
}
|
|
175
|
+
const intl = useIntl();
|
|
176
|
+
const { push, remove, removeAll } = ctx;
|
|
177
|
+
return useMemo(() => {
|
|
178
|
+
const add = (kind, message, options) => push(kind, resolveText(intl, message, options), options);
|
|
179
|
+
return {
|
|
180
|
+
addSuccess: (message, options) => add("success", message, options),
|
|
181
|
+
addInfo: (message, options) => add("info", message, options),
|
|
182
|
+
addWarning: (message, options) => add("warning", message, options),
|
|
183
|
+
addError: (message, options) => add("error", message, options),
|
|
184
|
+
add,
|
|
185
|
+
remove,
|
|
186
|
+
removeAll,
|
|
187
|
+
};
|
|
188
|
+
}, [intl, push, remove, removeAll]);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
export const UiToastContextInternal = UiToastContext;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type IUiToast } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export interface IUiToastItemProps {
|
|
7
|
+
toast: IUiToast;
|
|
8
|
+
/** Fired when the user dismisses (close X or action click). */
|
|
9
|
+
onClose: (id: string) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Presentational single-toast component. Most consumers use
|
|
13
|
+
* `UiToastsContainer` (which reads queued toasts from the provider), but
|
|
14
|
+
* this can be rendered directly for static demos or one-off composition.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function UiToastItem({ toast, onClose }: IUiToastItemProps): ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export interface IUiToastsContainerProps {
|
|
23
|
+
/** Test id forwarded to the root element. */
|
|
24
|
+
dataTestId?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Renders the list of toasts queued by `useUiToast`. Mount once near the app
|
|
28
|
+
* root, inside a `UiToastProvider`. Nested containers render nothing — only
|
|
29
|
+
* the outermost one is visible, preserving the topmost-only behaviour.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function UiToastsContainer({ dataTestId }: IUiToastsContainerProps): ReactNode;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { useContext, useMemo } from "react";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { commonDialogMessages } from "../../locales.js";
|
|
6
|
+
import { bem } from "../@utils/bem.js";
|
|
7
|
+
import { UiIcon } from "../UiIcon/UiIcon.js";
|
|
8
|
+
import { UiToastContextInternal } from "./UiToastProvider.js";
|
|
9
|
+
const { b, e } = bem("gd-ui-kit-toast");
|
|
10
|
+
// Errors and warnings interrupt screen readers by default; success/info wait
|
|
11
|
+
// for the assistive-tech queue to be free. Callers can override per toast.
|
|
12
|
+
const DEFAULT_ARIA_LIVE = {
|
|
13
|
+
success: "polite",
|
|
14
|
+
info: "polite",
|
|
15
|
+
warning: "assertive",
|
|
16
|
+
error: "assertive",
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Presentational single-toast component. Most consumers use
|
|
20
|
+
* `UiToastsContainer` (which reads queued toasts from the provider), but
|
|
21
|
+
* this can be rendered directly for static demos or one-off composition.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export function UiToastItem({ toast, onClose }) {
|
|
26
|
+
const intl = useIntl();
|
|
27
|
+
const ariaLive = toast.accessibilityConfig?.ariaLive ?? DEFAULT_ARIA_LIVE[toast.kind];
|
|
28
|
+
const role = toast.accessibilityConfig?.role ?? (ariaLive === "assertive" ? "alert" : "status");
|
|
29
|
+
const closeLabel = toast.accessibilityConfig?.closeButtonLabel ?? intl.formatMessage(commonDialogMessages.close);
|
|
30
|
+
return (_jsxs("div", { className: e("item", { kind: toast.kind }), role: role, "aria-live": ariaLive, "aria-label": toast.accessibilityConfig?.ariaLabel, "aria-labelledby": toast.accessibilityConfig?.ariaLabelledBy, "aria-describedby": toast.accessibilityConfig?.ariaDescribedBy, "data-testid": `gd-ui-kit-toast-${toast.kind}-${toast.id}`, children: [
|
|
31
|
+
_jsx("span", { className: e("body"), children: toast.text }), toast.action ? (_jsx("button", { type: "button", className: e("action"), onClick: () => {
|
|
32
|
+
toast.action.onClick();
|
|
33
|
+
onClose(toast.id);
|
|
34
|
+
}, children: toast.action.label })) : null, _jsx("button", { type: "button", className: e("close"), onClick: () => onClose(toast.id), "aria-label": closeLabel, children: _jsx(UiIcon, { type: "cross", size: 14, color: "currentColor" }) })
|
|
35
|
+
] }));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders the list of toasts queued by `useUiToast`. Mount once near the app
|
|
39
|
+
* root, inside a `UiToastProvider`. Nested containers render nothing — only
|
|
40
|
+
* the outermost one is visible, preserving the topmost-only behaviour.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function UiToastsContainer({ dataTestId }) {
|
|
45
|
+
const ctx = useContext(UiToastContextInternal);
|
|
46
|
+
// Render newest-first so the most recent notification sits at the top
|
|
47
|
+
// of the stack — matches the legacy `ToastsCenter` overlay
|
|
48
|
+
// (`messages.sort((a, b) => b.createdAt - a.createdAt)`). The queue
|
|
49
|
+
// itself stays in insertion order for `push` / `remove` correctness;
|
|
50
|
+
// only the rendered list is reversed.
|
|
51
|
+
const items = useMemo(() => (ctx?.toasts ? [...ctx.toasts].reverse() : []), [ctx?.toasts]);
|
|
52
|
+
if (!ctx || ctx.isNested) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return (_jsx("div", { className: b(), "data-testid": dataTestId, children: items.map((toast) => (_jsx(UiToastItem, { toast: toast, onClose: ctx.remove }, toast.id))) }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { type MessageDescriptor, type PrimitiveType } from "react-intl";
|
|
2
|
+
import { type IAccessibilityConfigBase } from "../../typings/accessibility.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interpolation values accepted by `react-intl`'s `formatMessage`, **narrowed
|
|
5
|
+
* to plain primitives** for toast use. Toast text is plain text by contract,
|
|
6
|
+
* so React-element interpolation values (`<b>name</b>`) are intentionally
|
|
7
|
+
* disallowed — `formatMessage` would otherwise return `ReactNode[]` which our
|
|
8
|
+
* downstream `text: string` would have to stringify, producing
|
|
9
|
+
* `"[object Object]"` for any element. Reject at compile time instead.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export type UiToastIntlValues = Record<string, PrimitiveType>;
|
|
14
|
+
/**
|
|
15
|
+
* Severity / visual kind of a toast.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export type UiToastKind = "success" | "info" | "warning" | "error";
|
|
20
|
+
/**
|
|
21
|
+
* Toast text content. Either a plain string or an `intl` message descriptor
|
|
22
|
+
* with optional interpolation values.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export type UiToastMessage = string | {
|
|
27
|
+
descriptor: MessageDescriptor;
|
|
28
|
+
values?: UiToastIntlValues;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Optional per-toast accessibility overrides. The container is a polite live
|
|
32
|
+
* region by default; `error` and `warning` toasts upgrade to assertive
|
|
33
|
+
* `role="alert"` unless overridden here.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export interface IUiToastAccessibilityConfig extends Pick<IAccessibilityConfigBase, "ariaLabel" | "ariaLabelledBy" | "ariaDescribedBy" | "role"> {
|
|
38
|
+
ariaLive?: "off" | "polite" | "assertive";
|
|
39
|
+
/**
|
|
40
|
+
* Accessible name for the close button. Defaults to the localized
|
|
41
|
+
* "Close" — pass a context-specific string (e.g. "Dismiss the
|
|
42
|
+
* 'access updated' notification") when a generic label isn't
|
|
43
|
+
* descriptive enough for screen-reader users.
|
|
44
|
+
*/
|
|
45
|
+
closeButtonLabel?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Optional inline action rendered next to the toast text (e.g. "Undo").
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export interface IUiToastAction {
|
|
53
|
+
/** Visible label on the action button. */
|
|
54
|
+
label: string;
|
|
55
|
+
/** Fired when the action is activated. */
|
|
56
|
+
onClick: () => void;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options accepted by `useUiToast`'s `add*` methods (and the generic `add`).
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export interface IUiToastOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Caller-supplied id. When omitted a stable id is generated. Use to
|
|
66
|
+
* dedupe or to remove a specific toast programmatically.
|
|
67
|
+
*/
|
|
68
|
+
id?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Skip auto-dismiss — the toast stays until the user closes it or
|
|
71
|
+
* `remove(id)` is called. Defaults to `false`.
|
|
72
|
+
*/
|
|
73
|
+
sticky?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Auto-dismiss delay in milliseconds. Ignored when `sticky` is true.
|
|
76
|
+
* Defaults to 4000 ms.
|
|
77
|
+
*/
|
|
78
|
+
durationMs?: number;
|
|
79
|
+
/** Optional inline action rendered next to the message. */
|
|
80
|
+
action?: IUiToastAction;
|
|
81
|
+
/**
|
|
82
|
+
* Optional intl interpolation values used when `message` is a string —
|
|
83
|
+
* exposed primarily so the kind-specific `add*` helpers can forward
|
|
84
|
+
* intl values supplied alongside a `MessageDescriptor`.
|
|
85
|
+
*/
|
|
86
|
+
values?: UiToastIntlValues;
|
|
87
|
+
/** Fires once when the toast is dismissed (auto or user). */
|
|
88
|
+
onDismiss?: () => void;
|
|
89
|
+
/** Per-toast accessibility overrides. */
|
|
90
|
+
accessibilityConfig?: IUiToastAccessibilityConfig;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A toast as the container renders it. Internal — not part of the public API.
|
|
94
|
+
*
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
export interface IUiToast {
|
|
98
|
+
id: string;
|
|
99
|
+
kind: UiToastKind;
|
|
100
|
+
/** Resolved display text (already passed through `formatMessage` if needed). */
|
|
101
|
+
text: string;
|
|
102
|
+
sticky: boolean;
|
|
103
|
+
durationMs: number;
|
|
104
|
+
action?: IUiToastAction;
|
|
105
|
+
accessibilityConfig?: IUiToastAccessibilityConfig;
|
|
106
|
+
onDismiss?: () => void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Public hook surface. Each `add*` returns the toast id, which callers can
|
|
110
|
+
* pass to `remove` to dismiss it programmatically.
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export interface IUseUiToastResult {
|
|
115
|
+
addSuccess: (message: UiToastMessage, options?: IUiToastOptions) => string;
|
|
116
|
+
addInfo: (message: UiToastMessage, options?: IUiToastOptions) => string;
|
|
117
|
+
addWarning: (message: UiToastMessage, options?: IUiToastOptions) => string;
|
|
118
|
+
addError: (message: UiToastMessage, options?: IUiToastOptions) => string;
|
|
119
|
+
/** Generic add — provide your own `kind`. */
|
|
120
|
+
add: (kind: UiToastKind, message: UiToastMessage, options?: IUiToastOptions) => string;
|
|
121
|
+
/** Dismiss a single toast by id. No-op if no toast with that id is showing. */
|
|
122
|
+
remove: (id: string) => void;
|
|
123
|
+
/** Dismiss all currently showing toasts. */
|
|
124
|
+
removeAll: () => void;
|
|
125
|
+
}
|
|
@@ -12,6 +12,7 @@ export declare class FlexDimensions extends Component<IFlexDimensionsProps, IFle
|
|
|
12
12
|
};
|
|
13
13
|
private wrapperRef;
|
|
14
14
|
private readonly throttledUpdateSize;
|
|
15
|
+
private readonly resizeObserver;
|
|
15
16
|
constructor(props: IFlexDimensionsProps);
|
|
16
17
|
componentDidMount(): void;
|
|
17
18
|
componentWillUnmount(): void;
|
|
@@ -16,6 +16,7 @@ export class FlexDimensions extends Component {
|
|
|
16
16
|
};
|
|
17
17
|
wrapperRef = createRef();
|
|
18
18
|
throttledUpdateSize;
|
|
19
|
+
resizeObserver;
|
|
19
20
|
constructor(props) {
|
|
20
21
|
super(props);
|
|
21
22
|
this.state = {
|
|
@@ -23,14 +24,17 @@ export class FlexDimensions extends Component {
|
|
|
23
24
|
height: 0,
|
|
24
25
|
};
|
|
25
26
|
this.throttledUpdateSize = throttle(this.updateSize, 250, { leading: false });
|
|
27
|
+
this.resizeObserver = new ResizeObserver(this.throttledUpdateSize);
|
|
26
28
|
}
|
|
27
29
|
componentDidMount() {
|
|
28
|
-
|
|
30
|
+
if (this.wrapperRef.current) {
|
|
31
|
+
this.resizeObserver.observe(this.wrapperRef.current);
|
|
32
|
+
}
|
|
29
33
|
this.throttledUpdateSize();
|
|
30
34
|
}
|
|
31
35
|
componentWillUnmount() {
|
|
32
36
|
this.throttledUpdateSize.cancel();
|
|
33
|
-
|
|
37
|
+
this.resizeObserver.disconnect();
|
|
34
38
|
}
|
|
35
39
|
getChildrenDimensions() {
|
|
36
40
|
return pickBy(this.state, (_, key) => {
|
|
@@ -65,7 +65,7 @@ function createInsightsItemsGroup(featureFlags, workspaceId, workspacePermission
|
|
|
65
65
|
const insightItemsGroup = [];
|
|
66
66
|
const kpisUrl = kpisItemUrl(baseUrl, workspaceId);
|
|
67
67
|
pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_KPIS_NEW, "s-menu-kpis", kpisUrl), canShowKpisItem(workspacePermissions, hasAnalyticalDashboards));
|
|
68
|
-
const analyzeUrl = analyzeItemUrl(baseUrl, workspaceId);
|
|
68
|
+
const analyzeUrl = analyzeItemUrl(baseUrl, workspaceId, featureFlags);
|
|
69
69
|
pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_ANALYZE, "s-menu-analyze", analyzeUrl), canShowAnalyzeItem(workspacePermissions));
|
|
70
70
|
const measuresUrl = measuresItemUrl(baseUrl, workspaceId, featureFlags);
|
|
71
71
|
pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_METRICS, "s-menu-metrics", measuresUrl), canShowMetricsItem(hasMeasures, workspacePermissions));
|
|
@@ -124,7 +124,10 @@ function kpisItemUrl(baseUrl, workspaceId) {
|
|
|
124
124
|
function canShowKpisItem(workspacePermissions, hasAnalyticalDashboards) {
|
|
125
125
|
return Boolean(hasAnalyticalDashboards || workspacePermissions.canCreateAnalyticalDashboard === true);
|
|
126
126
|
}
|
|
127
|
-
function analyzeItemUrl(baseUrl, workspaceId) {
|
|
127
|
+
function analyzeItemUrl(baseUrl, workspaceId, featureFlags) {
|
|
128
|
+
if (featureFlags.enableShellApplication_analyticalDesigner) {
|
|
129
|
+
return withBaseUrl(baseUrl, `/workspace/${workspaceId}/analyze/#/reportId/edit`);
|
|
130
|
+
}
|
|
128
131
|
return withBaseUrl(baseUrl, `/analyze/#/${workspaceId}/reportId/edit`);
|
|
129
132
|
}
|
|
130
133
|
function canShowAnalyzeItem(workspacePermissions) {
|
|
@@ -92,13 +92,15 @@ export class ShortenedText extends PureComponent {
|
|
|
92
92
|
if (elementWidth > 0 && elementWidth < element.scrollWidth) {
|
|
93
93
|
this.setState({ title: getShortenedTitle(title, element, ellipsisPosition), customTitle: true });
|
|
94
94
|
}
|
|
95
|
+
else {
|
|
96
|
+
this.setState({
|
|
97
|
+
title: this.props.children,
|
|
98
|
+
customTitle: false,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
102
|
recomputeShortening() {
|
|
97
|
-
|
|
98
|
-
this.setState({
|
|
99
|
-
title: this.props.children,
|
|
100
|
-
customTitle: false,
|
|
101
|
-
});
|
|
103
|
+
this.checkTitle();
|
|
102
104
|
}
|
|
103
105
|
renderTextWithBubble() {
|
|
104
106
|
return (_jsxs(BubbleHoverTrigger, { showDelay: 0, hideDelay: 0, eventsOnBubble: this.props.tooltipVisibleOnMouseOver, children: [
|
package/esm/index.d.ts
CHANGED
|
@@ -468,3 +468,6 @@ export { UiLabelsList, type IUiLabelsListProps, type IUiLabelsListItem, } from "
|
|
|
468
468
|
export { UiLabelsPicker, isLabelsPickerItemChecked, type IUiLabelsPickerProps, type IUiLabelsPickerItem, } from "./@ui/UiLabelsPicker/UiLabelsPicker.js";
|
|
469
469
|
export { UiPermissionMenu, type IUiPermissionMenuProps, type PermissionMenuLevel, } from "./@ui/UiPermissionMenu/UiPermissionMenu.js";
|
|
470
470
|
export { UiGranteeRowControls, type IUiGranteeRowControlsProps, } from "./@ui/UiGranteeRowControls/UiGranteeRowControls.js";
|
|
471
|
+
export { UiToastProvider, useUiToast } from "./@ui/UiToast/UiToastProvider.js";
|
|
472
|
+
export { UiToastItem, UiToastsContainer, type IUiToastItemProps, type IUiToastsContainerProps, } from "./@ui/UiToast/UiToastsContainer.js";
|
|
473
|
+
export { type IUiToast, type IUiToastAccessibilityConfig, type IUiToastAction, type IUiToastOptions, type IUseUiToastResult, type UiToastIntlValues, type UiToastKind, type UiToastMessage, } from "./@ui/UiToast/types.js";
|
package/esm/index.js
CHANGED
|
@@ -423,3 +423,5 @@ export { UiLabelsList, } from "./@ui/UiLabelsList/UiLabelsList.js";
|
|
|
423
423
|
export { UiLabelsPicker, isLabelsPickerItemChecked, } from "./@ui/UiLabelsPicker/UiLabelsPicker.js";
|
|
424
424
|
export { UiPermissionMenu, } from "./@ui/UiPermissionMenu/UiPermissionMenu.js";
|
|
425
425
|
export { UiGranteeRowControls, } from "./@ui/UiGranteeRowControls/UiGranteeRowControls.js";
|
|
426
|
+
export { UiToastProvider, useUiToast } from "./@ui/UiToast/UiToastProvider.js";
|
|
427
|
+
export { UiToastItem, UiToastsContainer, } from "./@ui/UiToast/UiToastsContainer.js";
|