@xsolla/xui-b2b-notification-panel 0.176.1 → 0.177.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/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  A full-width horizontal notification bar for persistent inline feedback within B2B page layouts. Stays in place until dismissed and is designed to sit inline within a page or panel rather than as a floating overlay.
4
4
 
5
+ Two layout variants are available via the `variant` prop:
6
+
7
+ - **`notification`** (default) — a taller banner with an icon frame on the left and a stacked title/description.
8
+ - **`announcement`** — a slim, single-line bar with an inline icon and the title/description on one row. Best for lightweight, page-level notices.
9
+
5
10
  For the base (non-B2B) variant, see [`@xsolla/xui-notification-panel`](./notification-panel.md).
6
11
 
7
12
  ## Installation
@@ -38,15 +43,16 @@ import { NotificationPanel } from '@xsolla/xui-b2b-notification-panel';
38
43
 
39
44
  | Prop | Type | Default | Description |
40
45
  | --- | --- | --- | --- |
46
+ | `variant` | `"notification" \| "announcement"` | `"notification"` | Layout. `notification` is the taller banner with an icon frame; `announcement` is the slim, single-line bar. |
41
47
  | `type` | `"alert" \| "warning" \| "success" \| "neutral" \| "brand"` | `"neutral"` | Visual tone of the panel. Controls background, default icon, and `role`. |
42
48
  | `title` | `string` | — | Bold title text. |
43
49
  | `description` | `string` | — | Secondary description text. |
44
- | `showIcon` | `boolean` | `true` | Show or hide the icon frame on the left. |
50
+ | `showIcon` | `boolean` | `true` | Show or hide the icon (the icon frame for `notification`, the inline icon for `announcement`). |
45
51
  | `icon` | `ReactNode` | — | Custom icon to replace the default type icon. |
46
- | `actionButton` | `ActionButtonElement` | — | A `Button` or `IconButton` element. `size="xs"` and `variant="secondary"` are injected via `cloneElement`; **`tone` is not** — set it yourself if needed. |
52
+ | `actionButton` | `ActionButtonElement` | — | A `Button`, `IconButton`, or `FlexButton` element. The panel injects styling via `cloneElement` (see below). |
47
53
  | `showCloseButton` | `boolean` | `true` | Show or hide the dismiss (×) button. |
48
54
  | `onClose` | `() => void` | — | Called when the close button is pressed. |
49
- | `closeButtonAlign` | `"top" \| "center"` | `"top"` | Vertical alignment of the close button. Use `"center"` for single-line panels. |
55
+ | `closeButtonAlign` | `"top" \| "center"` | `"top"` | Vertical alignment of the close button. Applies to the `notification` variant only (`announcement` is always centered). |
50
56
  | `testID` | `string` | — | Test ID for testing frameworks. |
51
57
 
52
58
  Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
@@ -54,10 +60,15 @@ Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
54
60
  ### `ActionButtonElement`
55
61
 
56
62
  ```ts
57
- type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps>;
63
+ type ActionButtonElement = React.ReactElement<
64
+ ButtonProps | IconButtonProps | FlexButtonProps
65
+ >;
58
66
  ```
59
67
 
60
- The element passed to `actionButton`. Only `size` and `variant` are overridden when the panel renders it; everything else (including `tone`, `onPress`, `icon`, etc.) is forwarded as authored.
68
+ The element passed to `actionButton`. The panel overrides a small set of props via `cloneElement` so the button matches the design; everything else (`onPress`, `icon`, `children`, etc.) is forwarded as authored. The injected props depend on `variant`:
69
+
70
+ - **`notification`** — `size="xs"`, `variant="tertiary"`, `tone="mono"`. Pass a `Button`.
71
+ - **`announcement`** — `size="xs"`, `variant="primary"`, `background={false}`. Pass a [`FlexButton`](./button.md) so it renders text-only with the designed subtle hover. (`FlexButton` has no `tone`; the Primary palette gives `content.primary` text.)
61
72
 
62
73
  ## Examples
63
74
 
@@ -77,7 +88,7 @@ import { NotificationPanel } from '@xsolla/xui-b2b-notification-panel';
77
88
 
78
89
  ### With action button
79
90
 
80
- Pick a `tone` that complements the panel's `type` — the panel does not auto-tone the button.
91
+ The panel styles the button to match the design (`size`, `variant`, and `tone`/`background` see [`ActionButtonElement`](#actionbuttonelement)), so pass a plain `Button`.
81
92
 
82
93
  ```tsx
83
94
  import { NotificationPanel } from '@xsolla/xui-b2b-notification-panel';
@@ -87,11 +98,25 @@ import { Button } from '@xsolla/xui-button';
87
98
  type="warning"
88
99
  title="Session expiring"
89
100
  description="Your session will expire in 5 minutes."
90
- actionButton={
91
- <Button tone="mono" onPress={() => extendSession()}>
92
- Extend session
93
- </Button>
94
- }
101
+ actionButton={<Button onPress={() => extendSession()}>Extend session</Button>}
102
+ onClose={() => dismiss()}
103
+ />;
104
+ ```
105
+
106
+ ### Announcement variant
107
+
108
+ A slim, single-line bar. Pass a [`FlexButton`](./button.md) as the action so it renders text-only.
109
+
110
+ ```tsx
111
+ import { NotificationPanel } from '@xsolla/xui-b2b-notification-panel';
112
+ import { FlexButton } from '@xsolla/xui-button';
113
+
114
+ <NotificationPanel
115
+ variant="announcement"
116
+ type="alert"
117
+ title="Alert notification"
118
+ description="Remove all product restrictions"
119
+ actionButton={<FlexButton onPress={() => openSettings()}>Button</FlexButton>}
95
120
  onClose={() => dismiss()}
96
121
  />;
97
122
  ```
@@ -155,6 +180,6 @@ import { NotificationPanel } from '@xsolla/xui-b2b-notification-panel';
155
180
  ## Accessibility
156
181
 
157
182
  - `role="alert"` for `type="alert"`; `role="status"` for all other types.
158
- - `aria-label` on the root is `"<type> notification"` (e.g. `"warning notification"`).
183
+ - `aria-label` on the root is `"<type> <variant>"` (e.g. `"warning notification"`, `"alert announcement"`).
159
184
  - The close button is an `IconButton` with `aria-label="Close notification"`.
160
185
  - The default type icons are decorative — pass `aria-hidden` on any custom `icon` you supply.
@@ -1,9 +1,17 @@
1
1
  import React from 'react';
2
2
  import { ThemeOverrideProps } from '@xsolla/xui-core';
3
- import { ButtonProps, IconButtonProps } from '@xsolla/xui-button';
3
+ import { ButtonProps, IconButtonProps, FlexButtonProps } from '@xsolla/xui-button';
4
4
 
5
- type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps>;
5
+ type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps | FlexButtonProps>;
6
6
  interface NotificationPanelProps extends ThemeOverrideProps {
7
+ /**
8
+ * Layout variant.
9
+ * - `notification` (default): full-height banner with an icon frame and a
10
+ * stacked title/description.
11
+ * - `announcement`: slim, single-line bar with an inline icon and the
12
+ * title/description on one row.
13
+ */
14
+ variant?: "notification" | "announcement";
7
15
  /** Visual variant/tone of the notification */
8
16
  type?: "alert" | "warning" | "success" | "neutral" | "brand";
9
17
  /** Title text (optional) */
package/native/index.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import React from 'react';
2
2
  import { ThemeOverrideProps } from '@xsolla/xui-core';
3
- import { ButtonProps, IconButtonProps } from '@xsolla/xui-button';
3
+ import { ButtonProps, IconButtonProps, FlexButtonProps } from '@xsolla/xui-button';
4
4
 
5
- type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps>;
5
+ type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps | FlexButtonProps>;
6
6
  interface NotificationPanelProps extends ThemeOverrideProps {
7
+ /**
8
+ * Layout variant.
9
+ * - `notification` (default): full-height banner with an icon frame and a
10
+ * stacked title/description.
11
+ * - `announcement`: slim, single-line bar with an inline icon and the
12
+ * title/description on one row.
13
+ */
14
+ variant?: "notification" | "announcement";
7
15
  /** Visual variant/tone of the notification */
8
16
  type?: "alert" | "warning" | "success" | "neutral" | "brand";
9
17
  /** Title text (optional) */
package/native/index.js CHANGED
@@ -208,6 +208,7 @@ var import_xui_typography = require("@xsolla/xui-typography");
208
208
  var import_xui_icons_base = require("@xsolla/xui-icons-base");
209
209
  var import_jsx_runtime2 = require("react/jsx-runtime");
210
210
  var NotificationPanel = ({
211
+ variant = "notification",
211
212
  type = "neutral",
212
213
  title,
213
214
  description,
@@ -226,6 +227,7 @@ var NotificationPanel = ({
226
227
  const typeConfig = {
227
228
  alert: {
228
229
  panelBg: theme.colors.overlay.alert,
230
+ announcementBg: theme.colors.background.alert.secondary,
229
231
  iconFrameBg: theme.colors.overlay.alert,
230
232
  iconColor: theme.colors.content.primary,
231
233
  buttonTone: "alert",
@@ -233,6 +235,7 @@ var NotificationPanel = ({
233
235
  },
234
236
  warning: {
235
237
  panelBg: theme.colors.overlay.warning,
238
+ announcementBg: theme.colors.background.warning.secondary,
236
239
  iconFrameBg: theme.colors.background.warning.primary,
237
240
  iconColor: theme.colors.content.primary,
238
241
  buttonTone: "mono",
@@ -240,6 +243,7 @@ var NotificationPanel = ({
240
243
  },
241
244
  success: {
242
245
  panelBg: theme.colors.overlay.success,
246
+ announcementBg: theme.colors.background.success.secondary,
243
247
  iconFrameBg: theme.colors.background.success.primary,
244
248
  iconColor: theme.colors.content.primary,
245
249
  buttonTone: "brandExtra",
@@ -247,6 +251,7 @@ var NotificationPanel = ({
247
251
  },
248
252
  neutral: {
249
253
  panelBg: theme.colors.overlay.mono,
254
+ announcementBg: theme.colors.background.neutral.secondary,
250
255
  iconFrameBg: theme.colors.overlay.mono,
251
256
  iconColor: theme.colors.content.primary,
252
257
  buttonTone: "mono",
@@ -254,6 +259,7 @@ var NotificationPanel = ({
254
259
  },
255
260
  brand: {
256
261
  panelBg: theme.colors.overlay.brand,
262
+ announcementBg: theme.colors.background.brand.secondary,
257
263
  iconFrameBg: theme.colors.overlay.brand,
258
264
  iconColor: theme.colors.content.primary,
259
265
  buttonTone: "brand",
@@ -262,6 +268,93 @@ var NotificationPanel = ({
262
268
  };
263
269
  const currentConfig = typeConfig[type];
264
270
  const IconComponent = currentConfig.IconComponent;
271
+ const ariaProps = {
272
+ role: type === "alert" ? "alert" : "status",
273
+ "aria-label": `${type} ${variant}`
274
+ };
275
+ if (variant === "announcement") {
276
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
277
+ Box,
278
+ {
279
+ backgroundColor: currentConfig.announcementBg,
280
+ flexDirection: "row",
281
+ alignItems: "center",
282
+ justifyContent: "center",
283
+ padding: config.announcementPadding,
284
+ gap: config.announcementGap,
285
+ overflow: "hidden",
286
+ testID,
287
+ style: {
288
+ backdropFilter: `blur(${config.announcementBlurRadius}px)`
289
+ },
290
+ ...ariaProps,
291
+ children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
293
+ Box,
294
+ {
295
+ flex: 1,
296
+ minWidth: 0,
297
+ overflow: "hidden",
298
+ flexDirection: "row",
299
+ alignItems: "center",
300
+ gap: config.announcementContentGap,
301
+ children: [
302
+ showIcon && (icon || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
303
+ IconComponent,
304
+ {
305
+ size: config.announcementIconSize,
306
+ color: currentConfig.iconColor,
307
+ variant: "solid"
308
+ }
309
+ )),
310
+ title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
311
+ import_xui_typography.Typography,
312
+ {
313
+ variant: "bodyXsAccent",
314
+ color: theme.colors.content.primary,
315
+ children: title
316
+ }
317
+ ),
318
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_typography.Typography, { variant: "bodyXs", color: theme.colors.content.secondary, children: description })
319
+ ]
320
+ }
321
+ ),
322
+ (actionButton || showCloseButton) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
323
+ Box,
324
+ {
325
+ flexShrink: 0,
326
+ flexDirection: "row",
327
+ alignItems: "center",
328
+ gap: config.announcementButtonsGap,
329
+ children: [
330
+ (0, import_react.isValidElement)(actionButton) && (0, import_react.cloneElement)(actionButton, {
331
+ size: "xs",
332
+ // Announcement uses a text-only FlexButton (Primary palette →
333
+ // content.primary text, no fill), per the Figma "Flex Button"
334
+ // node. hoverBackground is left at its default so the subtle
335
+ // mono hover overlay from the design is preserved.
336
+ variant: "primary",
337
+ background: false
338
+ }),
339
+ showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
340
+ import_xui_button.IconButton,
341
+ {
342
+ variant: "tertiary",
343
+ tone: "mono",
344
+ size: "xs",
345
+ onPress: onClose,
346
+ "aria-label": "Close notification",
347
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_icons_base.Remove, { size: config.announcementCloseIconSize }),
348
+ hoverBackground: "none"
349
+ }
350
+ )
351
+ ]
352
+ }
353
+ )
354
+ ]
355
+ }
356
+ );
357
+ }
265
358
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
266
359
  Box,
267
360
  {
@@ -271,8 +364,7 @@ var NotificationPanel = ({
271
364
  alignItems: "stretch",
272
365
  overflow: "hidden",
273
366
  testID,
274
- role: type === "alert" ? "alert" : "status",
275
- "aria-label": `${type} notification`,
367
+ ...ariaProps,
276
368
  children: [
277
369
  showIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
278
370
  Box,
@@ -328,7 +420,8 @@ var NotificationPanel = ({
328
420
  children: [
329
421
  (0, import_react.isValidElement)(actionButton) && (0, import_react.cloneElement)(actionButton, {
330
422
  size: "xs",
331
- variant: "secondary"
423
+ variant: "tertiary",
424
+ tone: "mono"
332
425
  }),
333
426
  showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
334
427
  import_xui_button.IconButton,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.tsx","../../src/NotificationPanel.tsx","../../../../foundation/primitives-native/src/Box.tsx"],"sourcesContent":["export { NotificationPanel } from \"./NotificationPanel\";\nexport type {\n NotificationPanelProps,\n ActionButtonElement,\n} from \"./NotificationPanel\";\n","import React, { cloneElement, isValidElement } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme, type ThemeOverrideProps } from \"@xsolla/xui-core\";\nimport {\n IconButton,\n type ButtonProps,\n type IconButtonProps,\n} from \"@xsolla/xui-button\";\nimport { Typography } from \"@xsolla/xui-typography\";\nimport {\n ExclamationMarkSq,\n InfoSq,\n CheckCr,\n Remove,\n type BaseIconComponent,\n} from \"@xsolla/xui-icons-base\";\n\nexport type ActionButtonElement = React.ReactElement<\n ButtonProps | IconButtonProps\n>;\n\nexport interface NotificationPanelProps extends ThemeOverrideProps {\n /** Visual variant/tone of the notification */\n type?: \"alert\" | \"warning\" | \"success\" | \"neutral\" | \"brand\";\n /** Title text (optional) */\n title?: string;\n /** Description text (optional) */\n description?: string;\n /** Show/hide the icon frame */\n showIcon?: boolean;\n /** Custom icon override (optional) */\n icon?: React.ReactNode;\n /**\n * Action button (optional - pass any Button/IconButton component).\n * The `tone`, `size`, and `variant` props will be automatically set.\n */\n actionButton?: React.ReactElement;\n /** Show/hide close button */\n showCloseButton?: boolean;\n /** Close button click handler */\n onClose?: () => void;\n /** Vertical alignment of the close button — \"top\" (default) or \"center\" */\n closeButtonAlign?: \"center\" | \"top\";\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\nexport const NotificationPanel: React.FC<NotificationPanelProps> = ({\n type = \"neutral\",\n title,\n description,\n showIcon = true,\n icon,\n actionButton,\n showCloseButton = true,\n onClose,\n closeButtonAlign = \"top\",\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const config = theme.sizing.notificationPanel();\n\n const typeConfig: Record<\n NonNullable<NotificationPanelProps[\"type\"]>,\n {\n panelBg: string;\n iconFrameBg: string;\n iconColor: string;\n buttonTone: \"brand\" | \"brandExtra\" | \"alert\" | \"mono\";\n IconComponent: BaseIconComponent;\n }\n > = {\n alert: {\n panelBg: theme.colors.overlay.alert,\n iconFrameBg: theme.colors.overlay.alert,\n iconColor: theme.colors.content.primary,\n buttonTone: \"alert\",\n IconComponent: ExclamationMarkSq,\n },\n warning: {\n panelBg: theme.colors.overlay.warning,\n iconFrameBg: theme.colors.background.warning.primary,\n iconColor: theme.colors.content.primary,\n buttonTone: \"mono\",\n IconComponent: ExclamationMarkSq,\n },\n success: {\n panelBg: theme.colors.overlay.success,\n iconFrameBg: theme.colors.background.success.primary,\n iconColor: theme.colors.content.primary,\n buttonTone: \"brandExtra\",\n IconComponent: CheckCr,\n },\n neutral: {\n panelBg: theme.colors.overlay.mono,\n iconFrameBg: theme.colors.overlay.mono,\n iconColor: theme.colors.content.primary,\n buttonTone: \"mono\",\n IconComponent: InfoSq,\n },\n brand: {\n panelBg: theme.colors.overlay.brand,\n iconFrameBg: theme.colors.overlay.brand,\n iconColor: theme.colors.content.primary,\n buttonTone: \"brand\",\n IconComponent: InfoSq,\n },\n };\n\n const currentConfig = typeConfig[type];\n const IconComponent = currentConfig.IconComponent;\n\n return (\n <Box\n backgroundColor={currentConfig.panelBg}\n borderRadius={config.borderRadius}\n flexDirection=\"row\"\n alignItems=\"stretch\"\n overflow=\"hidden\"\n testID={testID}\n role={type === \"alert\" ? \"alert\" : \"status\"}\n aria-label={`${type} notification`}\n >\n {/* Icon Frame */}\n {showIcon && (\n <Box\n backgroundColor={currentConfig.iconFrameBg}\n width={config.iconFrameWidth}\n alignItems=\"center\"\n justifyContent=\"center\"\n style={{\n borderTopLeftRadius: config.borderRadius,\n borderBottomLeftRadius: config.borderRadius,\n }}\n >\n {icon || (\n <IconComponent\n size={config.iconSize}\n color={currentConfig.iconColor}\n variant=\"solid\"\n />\n )}\n </Box>\n )}\n\n {/* Body */}\n <Box\n flex={1}\n flexDirection=\"row\"\n alignItems=\"center\"\n paddingHorizontal={config.bodyPaddingHorizontal}\n paddingVertical={config.bodyPaddingVertical}\n gap={config.contentGap}\n >\n {/* Text Content */}\n <Box flex={1} gap={config.textGap}>\n {title && (\n <Typography\n variant=\"bodyMdAccent\"\n color={theme.colors.content.primary}\n >\n {title}\n </Typography>\n )}\n {description && (\n <Typography variant=\"bodySm\" color={theme.colors.content.tertiary}>\n {description}\n </Typography>\n )}\n </Box>\n\n {/* Buttons */}\n {(actionButton || showCloseButton) && (\n <Box\n flexDirection=\"row\"\n alignSelf={closeButtonAlign === \"top\" ? \"stretch\" : \"center\"}\n alignItems={closeButtonAlign === \"top\" ? \"flex-start\" : \"center\"}\n justifyContent=\"flex-start\"\n paddingVertical={closeButtonAlign === \"top\" ? 4 : 0}\n gap={config.buttonsGap}\n >\n {isValidElement(actionButton) &&\n cloneElement(actionButton as ActionButtonElement, {\n size: \"xs\",\n variant: \"secondary\",\n })}\n\n {showCloseButton && (\n <IconButton\n variant=\"tertiary\"\n tone=\"mono\"\n size=\"xs\"\n onPress={onClose}\n aria-label=\"Close notification\"\n icon={<Remove size={18} />}\n hoverBackground=\"none\"\n />\n )}\n </Box>\n )}\n </Box>\n </Box>\n );\n};\n\nNotificationPanel.displayName = \"NotificationPanel\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n"],"mappings":"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","names":["import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/index.tsx","../../src/NotificationPanel.tsx","../../../../foundation/primitives-native/src/Box.tsx"],"sourcesContent":["export { NotificationPanel } from \"./NotificationPanel\";\nexport type {\n NotificationPanelProps,\n ActionButtonElement,\n} from \"./NotificationPanel\";\n","import React, { cloneElement, isValidElement } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme, type ThemeOverrideProps } from \"@xsolla/xui-core\";\nimport {\n IconButton,\n type ButtonProps,\n type IconButtonProps,\n type FlexButtonProps,\n} from \"@xsolla/xui-button\";\nimport { Typography } from \"@xsolla/xui-typography\";\nimport {\n ExclamationMarkSq,\n InfoSq,\n CheckCr,\n Remove,\n type BaseIconComponent,\n} from \"@xsolla/xui-icons-base\";\n\nexport type ActionButtonElement = React.ReactElement<\n ButtonProps | IconButtonProps | FlexButtonProps\n>;\n\nexport interface NotificationPanelProps extends ThemeOverrideProps {\n /**\n * Layout variant.\n * - `notification` (default): full-height banner with an icon frame and a\n * stacked title/description.\n * - `announcement`: slim, single-line bar with an inline icon and the\n * title/description on one row.\n */\n variant?: \"notification\" | \"announcement\";\n /** Visual variant/tone of the notification */\n type?: \"alert\" | \"warning\" | \"success\" | \"neutral\" | \"brand\";\n /** Title text (optional) */\n title?: string;\n /** Description text (optional) */\n description?: string;\n /** Show/hide the icon frame */\n showIcon?: boolean;\n /** Custom icon override (optional) */\n icon?: React.ReactNode;\n /**\n * Action button (optional - pass any Button/IconButton component).\n * The `tone`, `size`, and `variant` props will be automatically set.\n */\n actionButton?: React.ReactElement;\n /** Show/hide close button */\n showCloseButton?: boolean;\n /** Close button click handler */\n onClose?: () => void;\n /** Vertical alignment of the close button — \"top\" (default) or \"center\" */\n closeButtonAlign?: \"center\" | \"top\";\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\nexport const NotificationPanel: React.FC<NotificationPanelProps> = ({\n variant = \"notification\",\n type = \"neutral\",\n title,\n description,\n showIcon = true,\n icon,\n actionButton,\n showCloseButton = true,\n onClose,\n closeButtonAlign = \"top\",\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const config = theme.sizing.notificationPanel();\n\n const typeConfig: Record<\n NonNullable<NotificationPanelProps[\"type\"]>,\n {\n panelBg: string;\n /** Background of the slim announcement bar (and inline icon area). */\n announcementBg: string;\n iconFrameBg: string;\n iconColor: string;\n buttonTone: \"brand\" | \"brandExtra\" | \"alert\" | \"mono\";\n IconComponent: BaseIconComponent;\n }\n > = {\n alert: {\n panelBg: theme.colors.overlay.alert,\n announcementBg: theme.colors.background.alert.secondary,\n iconFrameBg: theme.colors.overlay.alert,\n iconColor: theme.colors.content.primary,\n buttonTone: \"alert\",\n IconComponent: ExclamationMarkSq,\n },\n warning: {\n panelBg: theme.colors.overlay.warning,\n announcementBg: theme.colors.background.warning.secondary,\n iconFrameBg: theme.colors.background.warning.primary,\n iconColor: theme.colors.content.primary,\n buttonTone: \"mono\",\n IconComponent: ExclamationMarkSq,\n },\n success: {\n panelBg: theme.colors.overlay.success,\n announcementBg: theme.colors.background.success.secondary,\n iconFrameBg: theme.colors.background.success.primary,\n iconColor: theme.colors.content.primary,\n buttonTone: \"brandExtra\",\n IconComponent: CheckCr,\n },\n neutral: {\n panelBg: theme.colors.overlay.mono,\n announcementBg: theme.colors.background.neutral.secondary,\n iconFrameBg: theme.colors.overlay.mono,\n iconColor: theme.colors.content.primary,\n buttonTone: \"mono\",\n IconComponent: InfoSq,\n },\n brand: {\n panelBg: theme.colors.overlay.brand,\n announcementBg: theme.colors.background.brand.secondary,\n iconFrameBg: theme.colors.overlay.brand,\n iconColor: theme.colors.content.primary,\n buttonTone: \"brand\",\n IconComponent: InfoSq,\n },\n };\n\n const currentConfig = typeConfig[type];\n const IconComponent = currentConfig.IconComponent;\n\n const ariaProps = {\n role: type === \"alert\" ? \"alert\" : \"status\",\n \"aria-label\": `${type} ${variant}`,\n } as const;\n\n if (variant === \"announcement\") {\n return (\n <Box\n backgroundColor={currentConfig.announcementBg}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n padding={config.announcementPadding}\n gap={config.announcementGap}\n overflow=\"hidden\"\n testID={testID}\n style={{\n backdropFilter: `blur(${config.announcementBlurRadius}px)`,\n }}\n {...ariaProps}\n >\n {/* Inline content: icon + title + description on a single row */}\n <Box\n flex={1}\n minWidth={0}\n overflow=\"hidden\"\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={config.announcementContentGap}\n >\n {showIcon &&\n (icon || (\n <IconComponent\n size={config.announcementIconSize}\n color={currentConfig.iconColor}\n variant=\"solid\"\n />\n ))}\n {title && (\n <Typography\n variant=\"bodyXsAccent\"\n color={theme.colors.content.primary}\n >\n {title}\n </Typography>\n )}\n {description && (\n <Typography variant=\"bodyXs\" color={theme.colors.content.secondary}>\n {description}\n </Typography>\n )}\n </Box>\n\n {/* Buttons */}\n {(actionButton || showCloseButton) && (\n <Box\n flexShrink={0}\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={config.announcementButtonsGap}\n >\n {isValidElement(actionButton) &&\n cloneElement(actionButton as ActionButtonElement, {\n size: \"xs\",\n // Announcement uses a text-only FlexButton (Primary palette →\n // content.primary text, no fill), per the Figma \"Flex Button\"\n // node. hoverBackground is left at its default so the subtle\n // mono hover overlay from the design is preserved.\n variant: \"primary\",\n background: false,\n })}\n\n {showCloseButton && (\n <IconButton\n variant=\"tertiary\"\n tone=\"mono\"\n size=\"xs\"\n onPress={onClose}\n aria-label=\"Close notification\"\n icon={<Remove size={config.announcementCloseIconSize} />}\n hoverBackground=\"none\"\n />\n )}\n </Box>\n )}\n </Box>\n );\n }\n\n return (\n <Box\n backgroundColor={currentConfig.panelBg}\n borderRadius={config.borderRadius}\n flexDirection=\"row\"\n alignItems=\"stretch\"\n overflow=\"hidden\"\n testID={testID}\n {...ariaProps}\n >\n {/* Icon Frame */}\n {showIcon && (\n <Box\n backgroundColor={currentConfig.iconFrameBg}\n width={config.iconFrameWidth}\n alignItems=\"center\"\n justifyContent=\"center\"\n style={{\n borderTopLeftRadius: config.borderRadius,\n borderBottomLeftRadius: config.borderRadius,\n }}\n >\n {icon || (\n <IconComponent\n size={config.iconSize}\n color={currentConfig.iconColor}\n variant=\"solid\"\n />\n )}\n </Box>\n )}\n\n {/* Body */}\n <Box\n flex={1}\n flexDirection=\"row\"\n alignItems=\"center\"\n paddingHorizontal={config.bodyPaddingHorizontal}\n paddingVertical={config.bodyPaddingVertical}\n gap={config.contentGap}\n >\n {/* Text Content */}\n <Box flex={1} gap={config.textGap}>\n {title && (\n <Typography\n variant=\"bodyMdAccent\"\n color={theme.colors.content.primary}\n >\n {title}\n </Typography>\n )}\n {description && (\n <Typography variant=\"bodySm\" color={theme.colors.content.tertiary}>\n {description}\n </Typography>\n )}\n </Box>\n\n {/* Buttons */}\n {(actionButton || showCloseButton) && (\n <Box\n flexDirection=\"row\"\n alignSelf={closeButtonAlign === \"top\" ? \"stretch\" : \"center\"}\n alignItems={closeButtonAlign === \"top\" ? \"flex-start\" : \"center\"}\n justifyContent=\"flex-start\"\n paddingVertical={closeButtonAlign === \"top\" ? 4 : 0}\n gap={config.buttonsGap}\n >\n {isValidElement(actionButton) &&\n cloneElement(actionButton as ActionButtonElement, {\n size: \"xs\",\n variant: \"tertiary\",\n tone: \"mono\",\n })}\n\n {showCloseButton && (\n <IconButton\n variant=\"tertiary\"\n tone=\"mono\"\n size=\"xs\"\n onPress={onClose}\n aria-label=\"Close notification\"\n icon={<Remove size={18} />}\n hoverBackground=\"none\"\n />\n )}\n </Box>\n )}\n </Box>\n </Box>\n );\n};\n\nNotificationPanel.displayName = \"NotificationPanel\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n"],"mappings":"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","names":["import_jsx_runtime"]}
package/native/index.mjs CHANGED
@@ -193,6 +193,7 @@ import {
193
193
  } from "@xsolla/xui-icons-base";
194
194
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
195
195
  var NotificationPanel = ({
196
+ variant = "notification",
196
197
  type = "neutral",
197
198
  title,
198
199
  description,
@@ -211,6 +212,7 @@ var NotificationPanel = ({
211
212
  const typeConfig = {
212
213
  alert: {
213
214
  panelBg: theme.colors.overlay.alert,
215
+ announcementBg: theme.colors.background.alert.secondary,
214
216
  iconFrameBg: theme.colors.overlay.alert,
215
217
  iconColor: theme.colors.content.primary,
216
218
  buttonTone: "alert",
@@ -218,6 +220,7 @@ var NotificationPanel = ({
218
220
  },
219
221
  warning: {
220
222
  panelBg: theme.colors.overlay.warning,
223
+ announcementBg: theme.colors.background.warning.secondary,
221
224
  iconFrameBg: theme.colors.background.warning.primary,
222
225
  iconColor: theme.colors.content.primary,
223
226
  buttonTone: "mono",
@@ -225,6 +228,7 @@ var NotificationPanel = ({
225
228
  },
226
229
  success: {
227
230
  panelBg: theme.colors.overlay.success,
231
+ announcementBg: theme.colors.background.success.secondary,
228
232
  iconFrameBg: theme.colors.background.success.primary,
229
233
  iconColor: theme.colors.content.primary,
230
234
  buttonTone: "brandExtra",
@@ -232,6 +236,7 @@ var NotificationPanel = ({
232
236
  },
233
237
  neutral: {
234
238
  panelBg: theme.colors.overlay.mono,
239
+ announcementBg: theme.colors.background.neutral.secondary,
235
240
  iconFrameBg: theme.colors.overlay.mono,
236
241
  iconColor: theme.colors.content.primary,
237
242
  buttonTone: "mono",
@@ -239,6 +244,7 @@ var NotificationPanel = ({
239
244
  },
240
245
  brand: {
241
246
  panelBg: theme.colors.overlay.brand,
247
+ announcementBg: theme.colors.background.brand.secondary,
242
248
  iconFrameBg: theme.colors.overlay.brand,
243
249
  iconColor: theme.colors.content.primary,
244
250
  buttonTone: "brand",
@@ -247,6 +253,93 @@ var NotificationPanel = ({
247
253
  };
248
254
  const currentConfig = typeConfig[type];
249
255
  const IconComponent = currentConfig.IconComponent;
256
+ const ariaProps = {
257
+ role: type === "alert" ? "alert" : "status",
258
+ "aria-label": `${type} ${variant}`
259
+ };
260
+ if (variant === "announcement") {
261
+ return /* @__PURE__ */ jsxs(
262
+ Box,
263
+ {
264
+ backgroundColor: currentConfig.announcementBg,
265
+ flexDirection: "row",
266
+ alignItems: "center",
267
+ justifyContent: "center",
268
+ padding: config.announcementPadding,
269
+ gap: config.announcementGap,
270
+ overflow: "hidden",
271
+ testID,
272
+ style: {
273
+ backdropFilter: `blur(${config.announcementBlurRadius}px)`
274
+ },
275
+ ...ariaProps,
276
+ children: [
277
+ /* @__PURE__ */ jsxs(
278
+ Box,
279
+ {
280
+ flex: 1,
281
+ minWidth: 0,
282
+ overflow: "hidden",
283
+ flexDirection: "row",
284
+ alignItems: "center",
285
+ gap: config.announcementContentGap,
286
+ children: [
287
+ showIcon && (icon || /* @__PURE__ */ jsx2(
288
+ IconComponent,
289
+ {
290
+ size: config.announcementIconSize,
291
+ color: currentConfig.iconColor,
292
+ variant: "solid"
293
+ }
294
+ )),
295
+ title && /* @__PURE__ */ jsx2(
296
+ Typography,
297
+ {
298
+ variant: "bodyXsAccent",
299
+ color: theme.colors.content.primary,
300
+ children: title
301
+ }
302
+ ),
303
+ description && /* @__PURE__ */ jsx2(Typography, { variant: "bodyXs", color: theme.colors.content.secondary, children: description })
304
+ ]
305
+ }
306
+ ),
307
+ (actionButton || showCloseButton) && /* @__PURE__ */ jsxs(
308
+ Box,
309
+ {
310
+ flexShrink: 0,
311
+ flexDirection: "row",
312
+ alignItems: "center",
313
+ gap: config.announcementButtonsGap,
314
+ children: [
315
+ isValidElement(actionButton) && cloneElement(actionButton, {
316
+ size: "xs",
317
+ // Announcement uses a text-only FlexButton (Primary palette →
318
+ // content.primary text, no fill), per the Figma "Flex Button"
319
+ // node. hoverBackground is left at its default so the subtle
320
+ // mono hover overlay from the design is preserved.
321
+ variant: "primary",
322
+ background: false
323
+ }),
324
+ showCloseButton && /* @__PURE__ */ jsx2(
325
+ IconButton,
326
+ {
327
+ variant: "tertiary",
328
+ tone: "mono",
329
+ size: "xs",
330
+ onPress: onClose,
331
+ "aria-label": "Close notification",
332
+ icon: /* @__PURE__ */ jsx2(Remove, { size: config.announcementCloseIconSize }),
333
+ hoverBackground: "none"
334
+ }
335
+ )
336
+ ]
337
+ }
338
+ )
339
+ ]
340
+ }
341
+ );
342
+ }
250
343
  return /* @__PURE__ */ jsxs(
251
344
  Box,
252
345
  {
@@ -256,8 +349,7 @@ var NotificationPanel = ({
256
349
  alignItems: "stretch",
257
350
  overflow: "hidden",
258
351
  testID,
259
- role: type === "alert" ? "alert" : "status",
260
- "aria-label": `${type} notification`,
352
+ ...ariaProps,
261
353
  children: [
262
354
  showIcon && /* @__PURE__ */ jsx2(
263
355
  Box,
@@ -313,7 +405,8 @@ var NotificationPanel = ({
313
405
  children: [
314
406
  isValidElement(actionButton) && cloneElement(actionButton, {
315
407
  size: "xs",
316
- variant: "secondary"
408
+ variant: "tertiary",
409
+ tone: "mono"
317
410
  }),
318
411
  showCloseButton && /* @__PURE__ */ jsx2(
319
412
  IconButton,