@xsolla/xui-b2b-notification-panel 0.190.0 → 0.192.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/web/index.d.mts CHANGED
@@ -3,6 +3,7 @@ import { ThemeOverrideProps } from '@xsolla/xui-core';
3
3
  import { ButtonProps, IconButtonProps, FlexButtonProps } from '@xsolla/xui-button';
4
4
 
5
5
  type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps | FlexButtonProps>;
6
+ type NotificationPanelDevice = "desktop" | "mobile";
6
7
  interface NotificationPanelProps extends ThemeOverrideProps {
7
8
  /**
8
9
  * Layout variant.
@@ -12,6 +13,15 @@ interface NotificationPanelProps extends ThemeOverrideProps {
12
13
  * title/description on one row.
13
14
  */
14
15
  variant?: "notification" | "announcement";
16
+ /**
17
+ * Device layout mode.
18
+ * - `desktop` (default): horizontal layout with actions on the right.
19
+ * - `mobile`: Figma mobile layout — `notification` uses a three-column row
20
+ * (icon | stacked text + action | close); `announcement` puts icon, title,
21
+ * on one row with description below; action and close are grouped on the
22
+ * right, top-aligned with the title row.
23
+ */
24
+ device?: NotificationPanelDevice;
15
25
  /** Visual variant/tone of the notification */
16
26
  type?: "alert" | "warning" | "success" | "neutral" | "brand";
17
27
  /** Title text (optional) */
@@ -23,18 +33,23 @@ interface NotificationPanelProps extends ThemeOverrideProps {
23
33
  /** Custom icon override (optional) */
24
34
  icon?: React.ReactNode;
25
35
  /**
26
- * Action button (optional - pass any Button/IconButton component).
27
- * The `tone`, `size`, and `variant` props will be automatically set.
36
+ * Action button (optional - pass any Button/IconButton/FlexButton component).
37
+ * `size` — plus `variant` and `background` for non-IconButton actions — falls
38
+ * back to a panel default, but any value you set on the button element itself
39
+ * takes precedence. `tone` is never overridden by the panel.
28
40
  */
29
41
  actionButton?: React.ReactElement;
30
42
  /** Show/hide close button */
31
43
  showCloseButton?: boolean;
32
44
  /** Close button click handler */
33
45
  onClose?: () => void;
34
- /** Vertical alignment of the close button — "top" (default) or "center" */
46
+ /**
47
+ * Vertical alignment of the action/close button group — "center" (default) or
48
+ * "top". Applies to the desktop `notification` variant only.
49
+ */
35
50
  closeButtonAlign?: "center" | "top";
36
51
  testID?: string;
37
52
  }
38
53
  declare const NotificationPanel: React.FC<NotificationPanelProps>;
39
54
 
40
- export { type ActionButtonElement, NotificationPanel, type NotificationPanelProps };
55
+ export { type ActionButtonElement, NotificationPanel, type NotificationPanelDevice, type NotificationPanelProps };
package/web/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { ThemeOverrideProps } from '@xsolla/xui-core';
3
3
  import { ButtonProps, IconButtonProps, FlexButtonProps } from '@xsolla/xui-button';
4
4
 
5
5
  type ActionButtonElement = React.ReactElement<ButtonProps | IconButtonProps | FlexButtonProps>;
6
+ type NotificationPanelDevice = "desktop" | "mobile";
6
7
  interface NotificationPanelProps extends ThemeOverrideProps {
7
8
  /**
8
9
  * Layout variant.
@@ -12,6 +13,15 @@ interface NotificationPanelProps extends ThemeOverrideProps {
12
13
  * title/description on one row.
13
14
  */
14
15
  variant?: "notification" | "announcement";
16
+ /**
17
+ * Device layout mode.
18
+ * - `desktop` (default): horizontal layout with actions on the right.
19
+ * - `mobile`: Figma mobile layout — `notification` uses a three-column row
20
+ * (icon | stacked text + action | close); `announcement` puts icon, title,
21
+ * on one row with description below; action and close are grouped on the
22
+ * right, top-aligned with the title row.
23
+ */
24
+ device?: NotificationPanelDevice;
15
25
  /** Visual variant/tone of the notification */
16
26
  type?: "alert" | "warning" | "success" | "neutral" | "brand";
17
27
  /** Title text (optional) */
@@ -23,18 +33,23 @@ interface NotificationPanelProps extends ThemeOverrideProps {
23
33
  /** Custom icon override (optional) */
24
34
  icon?: React.ReactNode;
25
35
  /**
26
- * Action button (optional - pass any Button/IconButton component).
27
- * The `tone`, `size`, and `variant` props will be automatically set.
36
+ * Action button (optional - pass any Button/IconButton/FlexButton component).
37
+ * `size` — plus `variant` and `background` for non-IconButton actions — falls
38
+ * back to a panel default, but any value you set on the button element itself
39
+ * takes precedence. `tone` is never overridden by the panel.
28
40
  */
29
41
  actionButton?: React.ReactElement;
30
42
  /** Show/hide close button */
31
43
  showCloseButton?: boolean;
32
44
  /** Close button click handler */
33
45
  onClose?: () => void;
34
- /** Vertical alignment of the close button — "top" (default) or "center" */
46
+ /**
47
+ * Vertical alignment of the action/close button group — "center" (default) or
48
+ * "top". Applies to the desktop `notification` variant only.
49
+ */
35
50
  closeButtonAlign?: "center" | "top";
36
51
  testID?: string;
37
52
  }
38
53
  declare const NotificationPanel: React.FC<NotificationPanelProps>;
39
54
 
40
- export { type ActionButtonElement, NotificationPanel, type NotificationPanelProps };
55
+ export { type ActionButtonElement, NotificationPanel, type NotificationPanelDevice, type NotificationPanelProps };
package/web/index.js CHANGED
@@ -296,6 +296,7 @@ var import_xui_icons_base = require("@xsolla/xui-icons-base");
296
296
  var import_jsx_runtime2 = require("react/jsx-runtime");
297
297
  var NotificationPanel = ({
298
298
  variant = "notification",
299
+ device = "desktop",
299
300
  type = "neutral",
300
301
  title,
301
302
  description,
@@ -304,13 +305,13 @@ var NotificationPanel = ({
304
305
  actionButton,
305
306
  showCloseButton = true,
306
307
  onClose,
307
- closeButtonAlign = "top",
308
+ closeButtonAlign = "center",
308
309
  testID,
309
310
  themeMode,
310
311
  themeProductContext
311
312
  }) => {
312
313
  const { theme } = (0, import_xui_core.useResolvedTheme)({ themeMode, themeProductContext });
313
- const config = theme.sizing.notificationPanel();
314
+ const config = theme.sizing.notificationPanel(device);
314
315
  const typeConfig = {
315
316
  alert: {
316
317
  panelBg: theme.colors.overlay.alert,
@@ -357,16 +358,129 @@ var NotificationPanel = ({
357
358
  const IconComponent = currentConfig.IconComponent;
358
359
  const ariaProps = {
359
360
  role: type === "alert" ? "alert" : "status",
360
- "aria-label": `${type} ${variant}`
361
+ "aria-label": `${type} ${variant} ${device}`
362
+ };
363
+ const renderCloseButton = (iconSize) => showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
364
+ import_xui_button.IconButton,
365
+ {
366
+ variant: "tertiary",
367
+ tone: "mono",
368
+ size: "xs",
369
+ onPress: onClose,
370
+ "aria-label": "Close notification",
371
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_icons_base.Remove, { size: iconSize }),
372
+ hoverBackground: "none"
373
+ }
374
+ ) : null;
375
+ const renderActionButton = () => {
376
+ if (!(0, import_react3.isValidElement)(actionButton)) return null;
377
+ const element = actionButton;
378
+ const isIconButton = element.type.displayName === "IconButton";
379
+ const props = element.props;
380
+ return (0, import_react3.cloneElement)(
381
+ element,
382
+ isIconButton ? { size: props.size ?? "xs" } : {
383
+ size: props.size ?? "xs",
384
+ // Non-IconButton actions default to a text-only FlexButton (Primary
385
+ // palette → content.primary text, no fill), per the Figma "Flex
386
+ // Button" node. hoverBackground is left at its default so the subtle
387
+ // mono hover overlay from the design is preserved.
388
+ variant: props.variant ?? "primary",
389
+ background: props.background ?? false
390
+ }
391
+ );
361
392
  };
362
393
  if (variant === "announcement") {
394
+ const isMobileAnnouncement = device === "mobile";
395
+ if (isMobileAnnouncement) {
396
+ const descriptionIndent = showIcon ? config.announcementIconSize + config.announcementContentGap : 0;
397
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
398
+ Box,
399
+ {
400
+ backgroundColor: currentConfig.announcementBg,
401
+ flexDirection: "row",
402
+ alignItems: "stretch",
403
+ padding: config.announcementPadding,
404
+ gap: config.announcementGap,
405
+ overflow: "hidden",
406
+ testID,
407
+ style: {
408
+ backdropFilter: `blur(${config.announcementBlurRadius}px)`
409
+ },
410
+ ...ariaProps,
411
+ children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
413
+ Box,
414
+ {
415
+ flex: 1,
416
+ minWidth: 0,
417
+ flexDirection: "column",
418
+ gap: config.textGap,
419
+ children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
421
+ Box,
422
+ {
423
+ flexDirection: "row",
424
+ alignItems: "flex-start",
425
+ gap: config.announcementContentGap,
426
+ children: [
427
+ showIcon && (icon || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
428
+ IconComponent,
429
+ {
430
+ size: config.announcementIconSize,
431
+ color: currentConfig.iconColor,
432
+ variant: "solid"
433
+ }
434
+ )),
435
+ title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box, { flex: 1, minWidth: 0, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
436
+ import_xui_typography.Typography,
437
+ {
438
+ variant: "bodyXsAccent",
439
+ color: theme.colors.content.primary,
440
+ noWrap: true,
441
+ children: title
442
+ }
443
+ ) })
444
+ ]
445
+ }
446
+ ),
447
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box, { paddingLeft: descriptionIndent, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
448
+ import_xui_typography.Typography,
449
+ {
450
+ variant: "bodyXs",
451
+ color: theme.colors.content.secondary,
452
+ noWrap: true,
453
+ children: description
454
+ }
455
+ ) })
456
+ ]
457
+ }
458
+ ),
459
+ (actionButton || showCloseButton) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
460
+ Box,
461
+ {
462
+ flexShrink: 0,
463
+ alignSelf: "flex-start",
464
+ flexDirection: "row",
465
+ alignItems: "center",
466
+ gap: config.announcementButtonsGap,
467
+ children: [
468
+ renderActionButton(),
469
+ renderCloseButton(config.announcementCloseIconSize)
470
+ ]
471
+ }
472
+ )
473
+ ]
474
+ }
475
+ );
476
+ }
363
477
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
364
478
  Box,
365
479
  {
366
480
  backgroundColor: currentConfig.announcementBg,
367
481
  flexDirection: "row",
368
482
  alignItems: "center",
369
- justifyContent: "center",
483
+ justifyContent: "flex-start",
370
484
  padding: config.announcementPadding,
371
485
  gap: config.announcementGap,
372
486
  overflow: "hidden",
@@ -394,15 +508,37 @@ var NotificationPanel = ({
394
508
  variant: "solid"
395
509
  }
396
510
  )),
397
- title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
398
- import_xui_typography.Typography,
511
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
512
+ Box,
399
513
  {
400
- variant: "bodyXsAccent",
401
- color: theme.colors.content.primary,
402
- children: title
514
+ flex: 1,
515
+ minWidth: 0,
516
+ overflow: "hidden",
517
+ flexDirection: "row",
518
+ alignItems: "center",
519
+ gap: config.announcementContentGap,
520
+ children: [
521
+ title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
522
+ import_xui_typography.Typography,
523
+ {
524
+ variant: "bodyXsAccent",
525
+ color: theme.colors.content.primary,
526
+ noWrap: true,
527
+ children: title
528
+ }
529
+ ),
530
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
531
+ import_xui_typography.Typography,
532
+ {
533
+ variant: "bodyXs",
534
+ color: theme.colors.content.secondary,
535
+ noWrap: true,
536
+ children: description
537
+ }
538
+ )
539
+ ]
403
540
  }
404
- ),
405
- description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_typography.Typography, { variant: "bodyXs", color: theme.colors.content.secondary, children: description })
541
+ )
406
542
  ]
407
543
  }
408
544
  ),
@@ -414,27 +550,8 @@ var NotificationPanel = ({
414
550
  alignItems: "center",
415
551
  gap: config.announcementButtonsGap,
416
552
  children: [
417
- (0, import_react3.isValidElement)(actionButton) && (0, import_react3.cloneElement)(actionButton, {
418
- size: "xs",
419
- // Announcement uses a text-only FlexButton (Primary palette →
420
- // content.primary text, no fill), per the Figma "Flex Button"
421
- // node. hoverBackground is left at its default so the subtle
422
- // mono hover overlay from the design is preserved.
423
- variant: "primary",
424
- background: false
425
- }),
426
- showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
427
- import_xui_button.IconButton,
428
- {
429
- variant: "tertiary",
430
- tone: "mono",
431
- size: "xs",
432
- onPress: onClose,
433
- "aria-label": "Close notification",
434
- icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_icons_base.Remove, { size: config.announcementCloseIconSize }),
435
- hoverBackground: "none"
436
- }
437
- )
553
+ renderActionButton(),
554
+ renderCloseButton(config.announcementCloseIconSize)
438
555
  ]
439
556
  }
440
557
  )
@@ -442,6 +559,89 @@ var NotificationPanel = ({
442
559
  }
443
560
  );
444
561
  }
562
+ if (device === "mobile") {
563
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
564
+ Box,
565
+ {
566
+ backgroundColor: currentConfig.panelBg,
567
+ borderRadius: config.borderRadius,
568
+ flexDirection: "row",
569
+ alignItems: "flex-start",
570
+ justifyContent: "space-between",
571
+ overflow: "hidden",
572
+ testID,
573
+ ...ariaProps,
574
+ children: [
575
+ showIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
576
+ Box,
577
+ {
578
+ alignSelf: "stretch",
579
+ paddingTop: config.mobileIconColumnPaddingVertical,
580
+ paddingBottom: config.mobileIconColumnPaddingVertical,
581
+ paddingLeft: config.mobileIconColumnPaddingLeft,
582
+ alignItems: "flex-start",
583
+ justifyContent: "flex-start",
584
+ children: icon || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
585
+ IconComponent,
586
+ {
587
+ size: config.mobileIconSize,
588
+ color: currentConfig.iconColor,
589
+ variant: "solid"
590
+ }
591
+ )
592
+ }
593
+ ),
594
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
595
+ Box,
596
+ {
597
+ flex: 1,
598
+ minWidth: 0,
599
+ paddingHorizontal: config.mobileBodyPaddingHorizontal,
600
+ paddingVertical: config.mobileBodyPaddingVertical,
601
+ flexDirection: "column",
602
+ alignItems: "flex-start",
603
+ justifyContent: "center",
604
+ gap: config.mobileBodyContentGap,
605
+ children: [
606
+ (title || description) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box, { alignSelf: "stretch", gap: config.textGap, children: [
607
+ title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
608
+ import_xui_typography.Typography,
609
+ {
610
+ variant: "bodySmAccent",
611
+ color: theme.colors.content.primary,
612
+ children: title
613
+ }
614
+ ),
615
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
616
+ import_xui_typography.Typography,
617
+ {
618
+ variant: "bodyXs",
619
+ color: theme.colors.content.tertiary,
620
+ children: description
621
+ }
622
+ )
623
+ ] }),
624
+ actionButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box, { marginLeft: config.mobileNotificationActionButtonOffsetLeft, children: renderActionButton() })
625
+ ]
626
+ }
627
+ ),
628
+ showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
629
+ Box,
630
+ {
631
+ alignSelf: "stretch",
632
+ flexDirection: "row",
633
+ paddingTop: config.mobileCloseColumnPaddingVertical,
634
+ paddingBottom: config.mobileCloseColumnPaddingVertical,
635
+ paddingRight: config.mobileCloseColumnPaddingRight,
636
+ alignItems: "flex-start",
637
+ justifyContent: "flex-end",
638
+ children: renderCloseButton(config.notificationCloseIconSize)
639
+ }
640
+ )
641
+ ]
642
+ }
643
+ );
644
+ }
445
645
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
446
646
  Box,
447
647
  {
@@ -499,29 +699,13 @@ var NotificationPanel = ({
499
699
  Box,
500
700
  {
501
701
  flexDirection: "row",
502
- alignSelf: closeButtonAlign === "top" ? "stretch" : "center",
503
- alignItems: closeButtonAlign === "top" ? "flex-start" : "center",
504
- justifyContent: "flex-start",
505
- paddingVertical: closeButtonAlign === "top" ? 4 : 0,
702
+ alignSelf: closeButtonAlign === "top" ? "flex-start" : void 0,
703
+ alignItems: "center",
704
+ flexShrink: 0,
506
705
  gap: config.buttonsGap,
507
706
  children: [
508
- (0, import_react3.isValidElement)(actionButton) && (0, import_react3.cloneElement)(actionButton, {
509
- size: "xs",
510
- variant: "tertiary",
511
- tone: "mono"
512
- }),
513
- showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
514
- import_xui_button.IconButton,
515
- {
516
- variant: "tertiary",
517
- tone: "mono",
518
- size: "xs",
519
- onPress: onClose,
520
- "aria-label": "Close notification",
521
- icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_icons_base.Remove, { size: 18 }),
522
- hoverBackground: "none"
523
- }
524
- )
707
+ renderActionButton(),
708
+ renderCloseButton(config.notificationCloseIconSize)
525
709
  ]
526
710
  }
527
711
  )