@ssa-ui-kit/core 3.8.0 → 3.9.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.
Files changed (32) hide show
  1. package/dist/components/Icon/icons/AttentionCircle.d.ts +3 -0
  2. package/dist/components/Icon/icons/all.d.ts +1 -0
  3. package/dist/components/Icon/icons/iconsList.d.ts +1 -1
  4. package/dist/components/NotificationComponents/Alert/Alert.d.ts +58 -0
  5. package/dist/components/NotificationComponents/Alert/AlertItem.d.ts +48 -0
  6. package/dist/components/NotificationComponents/Alert/alertObserver.d.ts +18 -0
  7. package/dist/components/NotificationComponents/Alert/index.d.ts +4 -0
  8. package/dist/components/NotificationComponents/Alert/styles.d.ts +18 -0
  9. package/dist/components/NotificationComponents/Alert/types.d.ts +80 -0
  10. package/dist/components/NotificationComponents/Notification/Notification.d.ts +4 -0
  11. package/dist/components/NotificationComponents/Notification/NotificationItem.d.ts +60 -0
  12. package/dist/components/NotificationComponents/Notification/index.d.ts +4 -0
  13. package/dist/components/NotificationComponents/Notification/notificationObserver.d.ts +23 -0
  14. package/dist/components/NotificationComponents/Notification/styles.d.ts +18 -0
  15. package/dist/components/NotificationComponents/Notification/types.d.ts +119 -0
  16. package/dist/components/NotificationComponents/Toast/Toast.d.ts +4 -0
  17. package/dist/components/NotificationComponents/Toast/ToastItem.d.ts +53 -0
  18. package/dist/components/NotificationComponents/Toast/index.d.ts +4 -0
  19. package/dist/components/NotificationComponents/Toast/styles.d.ts +26 -0
  20. package/dist/components/NotificationComponents/Toast/toastObserver.d.ts +30 -0
  21. package/dist/components/NotificationComponents/Toast/types.d.ts +102 -0
  22. package/dist/components/NotificationComponents/hooks/useAutoDismiss.d.ts +32 -0
  23. package/dist/components/NotificationComponents/index.d.ts +8 -0
  24. package/dist/components/NotificationComponents/styles.d.ts +22 -0
  25. package/dist/components/NotificationComponents/types.d.ts +38 -0
  26. package/dist/components/index.d.ts +1 -0
  27. package/dist/index.js +1772 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/types/emotion.d.ts +4 -0
  30. package/dist/utils/colorUtils.d.ts +45 -0
  31. package/dist/utils/createObserver.d.ts +21 -0
  32. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -7954,6 +7954,8 @@ __webpack_require__.d(__webpack_exports__, {
7954
7954
  AccordionGroupContextProvider: () => (/* reexport */ AccordionGroupContextProvider),
7955
7955
  AccordionTitle: () => (/* reexport */ AccordionTitle),
7956
7956
  AddNewAccountCard: () => (/* reexport */ AddNewAccountCard),
7957
+ Alert: () => (/* reexport */ Alert_Alert),
7958
+ AlertVariants: () => (/* reexport */ AlertVariants),
7957
7959
  Avatar: () => (/* reexport */ Avatar_Avatar),
7958
7960
  Badge: () => (/* reexport */ Badge_Badge),
7959
7961
  BarGaugeChart: () => (/* reexport */ BarGaugeChart),
@@ -8037,8 +8039,12 @@ __webpack_require__.d(__webpack_exports__, {
8037
8039
  NestedTableRow: () => (/* reexport */ NestedTableRow),
8038
8040
  NestedTableRowContext: () => (/* reexport */ NestedTableRowContext),
8039
8041
  NestedTableRowProvider: () => (/* reexport */ NestedTableRowProvider),
8042
+ Notification: () => (/* reexport */ NotificationComponents_Notification_Notification),
8040
8043
  NotificationCard: () => (/* reexport */ NotificationCard),
8041
8044
  NotificationMenu: () => (/* reexport */ NotificationMenu),
8045
+ NotificationPositions: () => (/* reexport */ NotificationPositions),
8046
+ NotificationSizes: () => (/* reexport */ NotificationSizes),
8047
+ NotificationVariants: () => (/* reexport */ NotificationVariants),
8042
8048
  NumberField: () => (/* reexport */ NumberField),
8043
8049
  PRESENT_VALUE: () => (/* reexport */ PRESENT_VALUE),
8044
8050
  Pagination: () => (/* reexport */ Pagination_Pagination),
@@ -8120,6 +8126,8 @@ __webpack_require__.d(__webpack_exports__, {
8120
8126
  Tag: () => (/* reexport */ Tag_Tag),
8121
8127
  TextField: () => (/* reexport */ TextField_TextField),
8122
8128
  Textarea: () => (/* reexport */ Textarea_Textarea),
8129
+ Toast: () => (/* reexport */ Toast_Toast),
8130
+ ToastVariants: () => (/* reexport */ ToastVariants),
8123
8131
  Tooltip: () => (/* reexport */ Tooltip_Tooltip),
8124
8132
  TooltipContent: () => (/* reexport */ TooltipContent_TooltipContent),
8125
8133
  TooltipContentBase: () => (/* reexport */ TooltipContentBase),
@@ -8160,6 +8168,9 @@ __webpack_require__.d(__webpack_exports__, {
8160
8168
  mainTheme: () => (/* reexport */ themes_main),
8161
8169
  pieChartPalettes: () => (/* reexport */ colorPalettes_namespaceObject),
8162
8170
  setHocDisplayName: () => (/* reexport */ setHocDisplayName),
8171
+ showAlert: () => (/* reexport */ showAlert),
8172
+ showNotification: () => (/* reexport */ showNotification),
8173
+ showToast: () => (/* reexport */ showToast),
8163
8174
  styleUtils: () => (/* reexport */ safari_focus_outline_namespaceObject),
8164
8175
  styles: () => (/* reexport */ Tooltip_styles_namespaceObject),
8165
8176
  useAccordionGroupContext: () => (/* reexport */ useAccordionGroupContext),
@@ -9244,6 +9255,14 @@ __webpack_require__.d(MoveFolder_namespaceObject, {
9244
9255
  MoveFolder: () => (MoveFolder)
9245
9256
  });
9246
9257
 
9258
+ // NAMESPACE OBJECT: ./src/components/Icon/icons/AttentionCircle.tsx
9259
+ var AttentionCircle_namespaceObject = {};
9260
+ __webpack_require__.r(AttentionCircle_namespaceObject);
9261
+ __webpack_require__.d(AttentionCircle_namespaceObject, {
9262
+ AttentionCircle: () => (AttentionCircle),
9263
+ ICON_NAME: () => (AttentionCircle_ICON_NAME)
9264
+ });
9265
+
9247
9266
  // NAMESPACE OBJECT: ./src/components/Icon/icons/all.ts
9248
9267
  var all_namespaceObject = {};
9249
9268
  __webpack_require__.r(all_namespaceObject);
@@ -9254,6 +9273,7 @@ __webpack_require__.d(all_namespaceObject, {
9254
9273
  ArrowUp: () => (ArrowUp_namespaceObject),
9255
9274
  Assessment: () => (Assessment_namespaceObject),
9256
9275
  Attention: () => (Attention_namespaceObject),
9276
+ AttentionCircle: () => (AttentionCircle_namespaceObject),
9257
9277
  Award: () => (Award_namespaceObject),
9258
9278
  BanUser: () => (BanUser_namespaceObject),
9259
9279
  Bench: () => (Bench_namespaceObject),
@@ -9534,12 +9554,16 @@ const main = {
9534
9554
  // #eb7556
9535
9555
  red6: 'rgba(235, 117, 86, 0.06)',
9536
9556
  // #eb7556
9557
+ red6RGB: 'rgb(254, 245, 246)',
9558
+ // #FEF5F6
9537
9559
  redDark: 'rgba(229, 53, 14, 1)',
9538
9560
  // #e5350e
9539
9561
  red40: 'rgba(235, 117, 86, 0.4)',
9540
9562
  // #eb7556
9541
9563
  greenLighter: 'rgba(137, 217, 150, 1)',
9542
9564
  // #89d996
9565
+ greenLighterRGB: 'rgb(246, 252, 247)',
9566
+ // #F6FCF7
9543
9567
  greenLighter6: 'rgba(137, 217, 150, 0.06)',
9544
9568
  // #89d996
9545
9569
  greenLighter20: 'rgba(137, 217, 150, 0.2)',
@@ -9586,6 +9610,8 @@ const main = {
9586
9610
  // #edba5d
9587
9611
  yellowLighter20: 'rgba(237, 186, 93, 0.2)',
9588
9612
  // #edba5d
9613
+ yellowLighter20RGB: 'rgb(254, 247, 242)',
9614
+ // #FEF7F2
9589
9615
  yellowLighter40: 'rgba(237, 186, 93, 0.4)',
9590
9616
  // #edba5d
9591
9617
  yellowWarm: 'rgba(237, 223, 93, 1)',
@@ -9624,6 +9650,8 @@ const main = {
9624
9650
  // #4178e1
9625
9651
  blue6: 'rgba(65, 120, 225, 0.06)',
9626
9652
  // #4178e1
9653
+ blue6RGB: 'rgb(244, 247, 252)',
9654
+ // #F4F7FC
9627
9655
  blue20: 'rgba(65, 120, 225, 0.2)',
9628
9656
  // #4178e1
9629
9657
  blueCool: 'rgba(108, 148, 247, 1)',
@@ -14042,6 +14070,36 @@ const MoveFolder = ({
14042
14070
  })]
14043
14071
  });
14044
14072
  const MoveFolder_ICON_NAME = 'moveFolder';
14073
+ ;// ./src/components/Icon/icons/AttentionCircle.tsx
14074
+
14075
+ const AttentionCircle = ({
14076
+ fill = '#000',
14077
+ size = 24,
14078
+ tooltip = 'Attention',
14079
+ ...props
14080
+ }) => (0,jsx_runtime_namespaceObject.jsxs)("svg", {
14081
+ xmlns: "http://www.w3.org/2000/svg",
14082
+ width: `${size}px`,
14083
+ height: `${size}px`,
14084
+ viewBox: "0 0 24 24",
14085
+ fill: "none",
14086
+ ...props,
14087
+ children: [(0,jsx_runtime_namespaceObject.jsx)("title", {
14088
+ children: tooltip
14089
+ }), (0,jsx_runtime_namespaceObject.jsx)("path", {
14090
+ d: "M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z",
14091
+ stroke: fill,
14092
+ strokeWidth: "1.4",
14093
+ strokeLinecap: "round",
14094
+ strokeLinejoin: "round"
14095
+ }), (0,jsx_runtime_namespaceObject.jsx)("path", {
14096
+ fillRule: "evenodd",
14097
+ clipRule: "evenodd",
14098
+ d: "M11.3052 7.75024V12.7502C11.3052 13.1368 11.6188 13.4504 12.0054 13.4504C12.392 13.4504 12.7056 13.1368 12.7056 12.7502V7.75024C12.7056 7.36364 12.392 7.05005 12.0054 7.05005C11.6188 7.05005 11.3052 7.36364 11.3052 7.75024ZM13.0088 15.7502C13.0088 15.198 12.5611 14.7502 12.0088 14.7502H12C11.4477 14.7502 11 15.198 11 15.7502C11 16.3025 11.4477 16.7502 12 16.7502H12.0088C12.5611 16.7502 13.0088 16.3025 13.0088 15.7502Z",
14099
+ fill: fill
14100
+ })]
14101
+ });
14102
+ const AttentionCircle_ICON_NAME = 'attention-circle';
14045
14103
  ;// ./src/components/Icon/icons/all.ts
14046
14104
 
14047
14105
 
@@ -14294,6 +14352,8 @@ const MoveFolder_ICON_NAME = 'moveFolder';
14294
14352
 
14295
14353
 
14296
14354
 
14355
+
14356
+
14297
14357
 
14298
14358
 
14299
14359
 
@@ -51474,6 +51534,1711 @@ const NotificationMenu = ({
51474
51534
  ;// ./src/components/NotificationMenu/index.ts
51475
51535
 
51476
51536
 
51537
+ ;// ./src/components/NotificationComponents/types.ts
51538
+ let NotificationPositions = /*#__PURE__*/function (NotificationPositions) {
51539
+ NotificationPositions["centerTop"] = "center-top";
51540
+ NotificationPositions["centerBottom"] = "center-bottom";
51541
+ NotificationPositions["leftTop"] = "left-top";
51542
+ NotificationPositions["leftBottom"] = "left-bottom";
51543
+ NotificationPositions["rightTop"] = "right-top";
51544
+ NotificationPositions["rightBottom"] = "right-bottom";
51545
+ return NotificationPositions;
51546
+ }({});
51547
+ let NotificationSizes = /*#__PURE__*/function (NotificationSizes) {
51548
+ NotificationSizes["small"] = "small";
51549
+ NotificationSizes["large"] = "large";
51550
+ return NotificationSizes;
51551
+ }({});
51552
+ ;// ./src/utils/createObserver.ts
51553
+ /**
51554
+ * Creates a lightweight pub/sub observer.
51555
+ *
51556
+ * Subscribers are stored in a Map keyed by a caller-provided string identifier,
51557
+ * so a single mount point can subscribe once and cleanly unsubscribe by key
51558
+ * without affecting other subscribers.
51559
+ *
51560
+ * @example
51561
+ * ```ts
51562
+ * const myObserver = createObserver<{ message: string }>();
51563
+ *
51564
+ * myObserver.subscribe('my-component', (data) => console.log(data.message));
51565
+ * myObserver.dispatch({ message: 'Hello' });
51566
+ * myObserver.unsubscribe('my-component');
51567
+ * ```
51568
+ */
51569
+ function createObserver() {
51570
+ const subscribers = new Map();
51571
+ const subscribe = (key, fn) => {
51572
+ subscribers.set(key, fn);
51573
+ };
51574
+ const unsubscribe = key => {
51575
+ subscribers.delete(key);
51576
+ };
51577
+ const dispatch = data => {
51578
+ subscribers.forEach(fn => fn(data));
51579
+ };
51580
+ return {
51581
+ subscribe,
51582
+ unsubscribe,
51583
+ dispatch
51584
+ };
51585
+ }
51586
+ ;// ./src/components/NotificationComponents/Alert/alertObserver.ts
51587
+ /**
51588
+ * Module-level singleton that connects imperative `showAlert()` calls to
51589
+ * whichever `<Alert>` components are currently mounted in the document.
51590
+ *
51591
+ * Architecture overview:
51592
+ * ```
51593
+ * showAlert(params)
51594
+ * └─► alertObserver.dispatch(params)
51595
+ * └─► Alert.tsx subscription callback (added in useEffect, removed on unmount)
51596
+ * └─► setAlerts(prev => [...]) — React state update → re-render → portal
51597
+ * ```
51598
+ *
51599
+ * Multiple `<Alert>` instances are supported (each subscribes under a unique
51600
+ * `useId()` key), so all mounted instances receive every dispatched event.
51601
+ * In a typical app you mount exactly one `<Alert>` near the root.
51602
+ */
51603
+
51604
+ const alertObserver = createObserver();
51605
+
51606
+ /**
51607
+ * Imperatively triggers a new alert inside the mounted `<Alert>` component.
51608
+ *
51609
+ * The `<Alert>` component must be mounted somewhere in the tree (typically near
51610
+ * the app root) to receive dispatched alerts.
51611
+ *
51612
+ * @example
51613
+ * ```ts
51614
+ * showAlert({ variant: AlertVariants.success, title: 'Saved!', description: 'Your changes have been saved.' });
51615
+ * ```
51616
+ */
51617
+ const showAlert = params => {
51618
+ alertObserver.dispatch(params);
51619
+ };
51620
+ ;// ./src/utils/colorUtils.ts
51621
+ /**
51622
+ * Color utilities shared by notification components (`Alert`, `Toast`, …).
51623
+ *
51624
+ * Used by any component that accepts a `color` prop and needs to auto-derive
51625
+ * readable text/icon colors, darkened border/accent shades, or luminance checks.
51626
+ *
51627
+ * Theme colors are `rgba(r, g, b, a)` strings. Arbitrary CSS colors passed by
51628
+ * consumers may be hex (`#rrggbb`) or any valid CSS color. Functions here handle
51629
+ * both formats and fall back gracefully (returning the original string) for
51630
+ * formats they cannot parse.
51631
+ */
51632
+
51633
+ /** Extracts [r, g, b] (0-255) from an rgba/rgb or #rrggbb string. */
51634
+ function parseRgb(color) {
51635
+ const rgbMatch = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
51636
+ if (rgbMatch) {
51637
+ return [parseInt(rgbMatch[1], 10), parseInt(rgbMatch[2], 10), parseInt(rgbMatch[3], 10)];
51638
+ }
51639
+ const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
51640
+ if (hexMatch) {
51641
+ return [parseInt(hexMatch[1], 16), parseInt(hexMatch[2], 16), parseInt(hexMatch[3], 16)];
51642
+ }
51643
+ return null;
51644
+ }
51645
+
51646
+ /**
51647
+ * Computes relative luminance (WCAG 2.x formula).
51648
+ * Returns a value in [0, 1] — 0 = black, 1 = white.
51649
+ */
51650
+ function getRelativeLuminance(rgb) {
51651
+ const [r, g, b] = rgb.map(c => {
51652
+ const s = c / 255;
51653
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
51654
+ });
51655
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
51656
+ }
51657
+
51658
+ /**
51659
+ * Returns `true` when the color is perceptually dark enough to need light
51660
+ * text/icons on top of it.
51661
+ *
51662
+ * Uses the WCAG luminance threshold (L < 0.179 ≈ midpoint between black and
51663
+ * "mid-grey" at contrast ratio ~3:1 against white).
51664
+ */
51665
+ function isColorDark(color) {
51666
+ const rgb = parseRgb(color);
51667
+ if (!rgb) return false;
51668
+ return getRelativeLuminance(rgb) < 0.179;
51669
+ }
51670
+
51671
+ // ─── HSL conversion helpers ───────────────────────────────────────────────────
51672
+
51673
+ /** Converts linearized sRGB [0-255] to [h, s, l] each in [0, 1]. */
51674
+ function rgbToHsl(r, g, b) {
51675
+ const rn = r / 255;
51676
+ const gn = g / 255;
51677
+ const bn = b / 255;
51678
+ const max = Math.max(rn, gn, bn);
51679
+ const min = Math.min(rn, gn, bn);
51680
+ let h = 0;
51681
+ let s = 0;
51682
+ const l = (max + min) / 2;
51683
+ if (max !== min) {
51684
+ const d = max - min;
51685
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
51686
+ if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;else if (max === gn) h = ((bn - rn) / d + 2) / 6;else h = ((rn - gn) / d + 4) / 6;
51687
+ }
51688
+ return [h, s, l];
51689
+ }
51690
+ function colorUtils_hue2rgb(p, q, t) {
51691
+ const tt = (t % 1 + 1) % 1; // normalize to [0, 1]
51692
+ if (tt < 1 / 6) return p + (q - p) * 6 * tt;
51693
+ if (tt < 1 / 2) return q;
51694
+ if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
51695
+ return p;
51696
+ }
51697
+
51698
+ /** Converts [h, s, l] (each in [0, 1]) to [r, g, b] (each in [0-255]). */
51699
+ function colorUtils_hslToRgb(h, s, l) {
51700
+ if (s === 0) {
51701
+ const v = Math.round(l * 255);
51702
+ return [v, v, v];
51703
+ }
51704
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
51705
+ const p = 2 * l - q;
51706
+ return [Math.round(colorUtils_hue2rgb(p, q, h + 1 / 3) * 255), Math.round(colorUtils_hue2rgb(p, q, h) * 255), Math.round(colorUtils_hue2rgb(p, q, h - 1 / 3) * 255)];
51707
+ }
51708
+
51709
+ /**
51710
+ * Returns a darkened version of the color by **reducing HSL lightness** by
51711
+ * `amount` (default 0.35 → 35 percentage points darker).
51712
+ *
51713
+ * Converting through HSL preserves the hue and saturation so light pastel
51714
+ * colors (e.g. `greenLighter`) darken to a recognizable mid-shade of the same
51715
+ * hue rather than washing out to grey (which a naïve per-channel multiply does).
51716
+ *
51717
+ * Falls back to the original string when the color format is not parsable
51718
+ * (e.g. CSS named colors, `hsl()`, `color-mix()`).
51719
+ */
51720
+ function darkenColor(color, amount = 0.35) {
51721
+ const rgb = parseRgb(color);
51722
+ if (!rgb) return color;
51723
+ const [h, s, l] = rgbToHsl(...rgb);
51724
+ const [r, g, b] = colorUtils_hslToRgb(h, s, Math.max(0, l - amount));
51725
+ return `rgb(${r}, ${g}, ${b})`;
51726
+ }
51727
+
51728
+ /**
51729
+ * Returns either `'rgba(255, 255, 255, 1)'` (white) or `'rgba(43, 45, 49, 1)'`
51730
+ * (greyDarker) depending on whether the background color is perceived as dark
51731
+ * or light.
51732
+ *
51733
+ * Intended for computing readable text/icon colors on a custom background.
51734
+ *
51735
+ * @param bgColor - The background color to contrast against.
51736
+ * @param darkText - The dark text color to use on light backgrounds.
51737
+ * Defaults to `rgba(43, 45, 49, 1)` (theme `greyDarker`).
51738
+ * @param lightText - The light text color to use on dark backgrounds.
51739
+ * Defaults to `rgba(255, 255, 255, 1)` (white).
51740
+ */
51741
+ function getContrastColor(bgColor, darkText = 'rgba(43, 45, 49, 1)', lightText = 'rgba(255, 255, 255, 1)') {
51742
+ return isColorDark(bgColor) ? lightText : darkText;
51743
+ }
51744
+ ;// ./src/components/NotificationComponents/Alert/types.ts
51745
+ let AlertVariants = /*#__PURE__*/function (AlertVariants) {
51746
+ AlertVariants["success"] = "success";
51747
+ AlertVariants["warning"] = "warning";
51748
+ AlertVariants["error"] = "error";
51749
+ AlertVariants["primary"] = "primary";
51750
+ AlertVariants["neutral"] = "neutral";
51751
+ AlertVariants["secondary"] = "secondary";
51752
+ return AlertVariants;
51753
+ }({});
51754
+
51755
+ /**
51756
+ * Per-slot style overrides for `<Alert>`.
51757
+ *
51758
+ * Because the alert cards are rendered inside a portal, a top-level `css` prop
51759
+ * cannot reach the inner elements. Use this object instead to customize any
51760
+ * part of the card without touching the global theme.
51761
+ *
51762
+ * CSS slots accept any `CSSObject` and are merged **after** the default styles,
51763
+ * so they always win. The two `*Color` fields exist because `<Icon>` receives
51764
+ * its fill via a prop rather than CSS inheritance.
51765
+ *
51766
+ * @example
51767
+ * ```tsx
51768
+ * <Alert
51769
+ * styles={{
51770
+ * root: { background: '#1a1a2e', borderRadius: 4 },
51771
+ * title: { color: '#ffffff', fontSize: 15 },
51772
+ * description: { color: 'rgba(255,255,255,0.65)' },
51773
+ * actionButton: { color: '#a78bfa' },
51774
+ * iconColor: '#a78bfa',
51775
+ * closeIconColor: 'rgba(255,255,255,0.5)',
51776
+ * }}
51777
+ * />
51778
+ * ```
51779
+ */
51780
+ ;// ./src/components/NotificationComponents/Alert/styles.ts
51781
+
51782
+
51783
+
51784
+ // ─── Re-export shared structural styles ──────────────────────────────────────
51785
+ // Consuming components (`AlertItem`, `Alert`) import `* as styles from './styles'`
51786
+ // so all shared exports need to be reachable from this file.
51787
+
51788
+
51789
+
51790
+ // ─── Variant color tokens ────────────────────────────────────────────────────
51791
+
51792
+ const getVariantTokens = (theme, variant) => {
51793
+ const map = {
51794
+ [AlertVariants.success]: {
51795
+ iconColor: theme.palette.success.main,
51796
+ accentColor: theme.palette.success.main,
51797
+ tintBg: `color-mix(in srgb, ${theme.palette.success.light} 8%, white)`
51798
+ },
51799
+ [AlertVariants.warning]: {
51800
+ iconColor: theme.palette.warning.main,
51801
+ accentColor: theme.palette.warning.main,
51802
+ tintBg: `color-mix(in srgb, ${theme.palette.warning.main} 8%, white)`
51803
+ },
51804
+ [AlertVariants.error]: {
51805
+ iconColor: theme.palette.error.main,
51806
+ accentColor: theme.palette.error.main,
51807
+ tintBg: `color-mix(in srgb, ${theme.palette.error.light} 8%, white)`
51808
+ },
51809
+ [AlertVariants.primary]: {
51810
+ iconColor: theme.palette.primary.main,
51811
+ accentColor: theme.palette.primary.main,
51812
+ tintBg: `color-mix(in srgb, ${theme.palette.primary.light} 8%, white)`
51813
+ },
51814
+ [AlertVariants.neutral]: {
51815
+ iconColor: theme.colors.greyDarker60,
51816
+ accentColor: theme.colors.grey,
51817
+ tintBg: theme.colors.white
51818
+ },
51819
+ [AlertVariants.secondary]: {
51820
+ iconColor: theme.colors.greyDarker60,
51821
+ accentColor: theme.colors.grey,
51822
+ tintBg: theme.palette.secondary.light
51823
+ }
51824
+ };
51825
+ return map[variant];
51826
+ };
51827
+ const variantIcons = {
51828
+ [AlertVariants.success]: 'check-circle',
51829
+ [AlertVariants.warning]: 'attention-circle',
51830
+ [AlertVariants.error]: 'attention-circle',
51831
+ [AlertVariants.primary]: 'information',
51832
+ [AlertVariants.neutral]: 'check-circle',
51833
+ [AlertVariants.secondary]: 'check-circle'
51834
+ };
51835
+
51836
+ // ─── Item wrapper ─────────────────────────────────────────────────────────────
51837
+
51838
+ const itemWrapperStyles = (background, hasDescription) => /*#__PURE__*/(0,react_namespaceObject.css)("position:relative;border-radius:8px;padding:12px 16px;display:flex;gap:10px;box-sizing:border-box;background:", background, ";align-items:", hasDescription ? 'flex-start' : 'center', ";" + ( true ? "" : 0), true ? "" : 0);
51839
+
51840
+ // ─── Text & action styles ─────────────────────────────────────────────────────
51841
+ // Accept an optional `textColor` so the `color` prop can override theme defaults
51842
+ // without a separate styleOverrides entry.
51843
+
51844
+ const titleTextStyles = (theme, textColor) => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:14px;font-weight:600;line-height:20px;color:", textColor ?? theme.colors.greyDarker, ";word-break:break-word;" + ( true ? "" : 0), true ? "" : 0);
51845
+ const descriptionStyles = (theme, textColor) => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:13px;font-weight:400;line-height:18px;color:", textColor ?? theme.colors.greyDarker80, ";margin:0;word-break:break-word;" + ( true ? "" : 0), true ? "" : 0);
51846
+ const actionBtnStyles = (theme, textColor) => /*#__PURE__*/(0,react_namespaceObject.css)("background:none;border:none;padding:0;cursor:pointer;font-size:13px;height:max-content;font-weight:500;line-height:18px;letter-spacing:0.2px;color:", textColor ?? theme.colors.greyDarker80, ";&:hover{color:", textColor ?? theme.colors.greyDarker, ";}" + ( true ? "" : 0), true ? "" : 0);
51847
+ ;// ./src/components/NotificationComponents/styles.ts
51848
+ function NotificationComponents_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
51849
+ /**
51850
+ * Shared structural styles for notification components (Alert, Toast, …).
51851
+ *
51852
+ * Everything here is purely layout / animation with no semantic color
51853
+ * meaning — safe to import from any notification component.
51854
+ *
51855
+ * Component-specific styles (variant tokens, item wrapper, text colors)
51856
+ * live in each component's own `styles.ts`.
51857
+ */
51858
+
51859
+
51860
+
51861
+
51862
+ // ─── Slide-in keyframes ──────────────────────────────────────────────────────
51863
+
51864
+ const slideFromTop = (0,react_namespaceObject.keyframes)`
51865
+ from { transform: translateY(-12px); opacity: 0; }
51866
+ to { transform: translateY(0); opacity: 1; }
51867
+ `;
51868
+ const slideFromBottom = (0,react_namespaceObject.keyframes)`
51869
+ from { transform: translateY(12px); opacity: 0; }
51870
+ to { transform: translateY(0); opacity: 1; }
51871
+ `;
51872
+ const slideFromLeft = (0,react_namespaceObject.keyframes)`
51873
+ from { transform: translateX(-12px); opacity: 0; }
51874
+ to { transform: translateX(0); opacity: 1; }
51875
+ `;
51876
+ const slideFromRight = (0,react_namespaceObject.keyframes)`
51877
+ from { transform: translateX(12px); opacity: 0; }
51878
+ to { transform: translateX(0); opacity: 1; }
51879
+ `;
51880
+ const animationByPosition = {
51881
+ [NotificationPositions.centerTop]: slideFromTop,
51882
+ [NotificationPositions.centerBottom]: slideFromBottom,
51883
+ [NotificationPositions.leftTop]: slideFromLeft,
51884
+ [NotificationPositions.leftBottom]: slideFromLeft,
51885
+ [NotificationPositions.rightTop]: slideFromRight,
51886
+ [NotificationPositions.rightBottom]: slideFromRight
51887
+ };
51888
+
51889
+ // ─── Portal container ────────────────────────────────────────────────────────
51890
+
51891
+ const positionMap = {
51892
+ [NotificationPositions.centerTop]: true ? {
51893
+ name: "b5myqp",
51894
+ styles: "top:16px;left:50%;transform:translateX(-50%);align-items:center"
51895
+ } : 0,
51896
+ [NotificationPositions.centerBottom]: true ? {
51897
+ name: "1s9nvkc",
51898
+ styles: "bottom:16px;left:50%;transform:translateX(-50%);align-items:center"
51899
+ } : 0,
51900
+ [NotificationPositions.leftTop]: true ? {
51901
+ name: "ll6dpi",
51902
+ styles: "top:16px;left:16px;align-items:flex-start"
51903
+ } : 0,
51904
+ [NotificationPositions.leftBottom]: true ? {
51905
+ name: "uv3867",
51906
+ styles: "bottom:16px;left:16px;align-items:flex-start"
51907
+ } : 0,
51908
+ [NotificationPositions.rightTop]: true ? {
51909
+ name: "1c0e6dl",
51910
+ styles: "top:16px;right:16px;align-items:flex-end"
51911
+ } : 0,
51912
+ [NotificationPositions.rightBottom]: true ? {
51913
+ name: "e07orh",
51914
+ styles: "bottom:16px;right:16px;align-items:flex-end"
51915
+ } : 0
51916
+ };
51917
+ const containerStyles = position => /*#__PURE__*/(0,react_namespaceObject.css)("position:fixed;z-index:9999;display:flex;flex-direction:column;gap:8px;", positionMap[position], ";" + ( true ? "" : 0), true ? "" : 0);
51918
+
51919
+ // ─── Card size ───────────────────────────────────────────────────────────────
51920
+
51921
+ const itemSizeStyles = {
51922
+ [NotificationSizes.small]: true ? {
51923
+ name: "1hdwerr",
51924
+ styles: "width:400px;max-width:calc(100vw - 32px)"
51925
+ } : 0,
51926
+ [NotificationSizes.large]: true ? {
51927
+ name: "1t8amrd",
51928
+ styles: "width:760px;max-width:calc(100vw - 32px)"
51929
+ } : 0
51930
+ };
51931
+
51932
+ // ─── Slide-in animation ───────────────────────────────────────────────────────
51933
+
51934
+ const itemAnimationStyles = (position, duration) => /*#__PURE__*/(0,react_namespaceObject.css)("animation:", animationByPosition[position], " ", duration, "ms ease-out forwards;" + ( true ? "" : 0), true ? "" : 0);
51935
+
51936
+ // ─── Shadow & border ─────────────────────────────────────────────────────────
51937
+
51938
+ const shadowStyles = theme => /*#__PURE__*/(0,react_namespaceObject.css)("box-shadow:0 4px 16px ", theme.colors.greyShadow, ";" + ( true ? "" : 0), true ? "" : 0);
51939
+ const borderStyles = borderColor => /*#__PURE__*/(0,react_namespaceObject.css)("border:1px solid ", borderColor, ";" + ( true ? "" : 0), true ? "" : 0);
51940
+
51941
+ // ─── Icon column ─────────────────────────────────────────────────────────────
51942
+
51943
+ const iconColStyles = true ? {
51944
+ name: "1wz4nr",
51945
+ styles: "display:flex;flex-shrink:0;padding-top:2px"
51946
+ } : 0;
51947
+
51948
+ // ─── Expanded layout (has description) ───────────────────────────────────────
51949
+
51950
+ const contentColStyles = true ? {
51951
+ name: "1u7mutw",
51952
+ styles: "display:flex;flex-direction:column;gap:6px;flex:1;min-width:0;align-self:flex-start"
51953
+ } : 0;
51954
+ const expandedHeaderRowStyles = true ? {
51955
+ name: "1yydxi7",
51956
+ styles: "display:flex;align-items:center;gap:8px"
51957
+ } : 0;
51958
+
51959
+ // ─── Collapsed layout (no description) ───────────────────────────────────────
51960
+
51961
+ const collapsedTitleStyles = true ? {
51962
+ name: "fxp7t8",
51963
+ styles: "flex:1;min-width:0"
51964
+ } : 0;
51965
+
51966
+ // ─── Actions row & close button ───────────────────────────────────────────────
51967
+
51968
+ const actionsRowStyles = true ? {
51969
+ name: "1yydxi7",
51970
+ styles: "display:flex;align-items:center;gap:8px"
51971
+ } : 0;
51972
+ const closeBtnStyles = true ? {
51973
+ name: "13odyj7",
51974
+ styles: "background:none;border:none;padding:2px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;line-height:0;opacity:0.7;&:hover{opacity:1;}"
51975
+ } : 0;
51976
+ ;// ./src/components/NotificationComponents/Alert/AlertItem.tsx
51977
+
51978
+
51979
+
51980
+
51981
+
51982
+
51983
+ /**
51984
+ * @internal
51985
+ *
51986
+ * Renders a single alert card inside the `<Alert>` portal container.
51987
+ * This component is **not part of the public API** — consumers should call
51988
+ * `showAlert(params)` to trigger alerts and configure appearance via the
51989
+ * `<Alert>` component's props (`withShadow`, `withBorder`, `styles`, etc.).
51990
+ *
51991
+ * Direct use of `AlertItem` is only intended for static previews in Storybook
51992
+ * stories or tests where portal/observer overhead is unwanted.
51993
+ *
51994
+ * Layout modes:
51995
+ * - **Expanded** — when `description` is provided: title + description + action row + close button in header
51996
+ * - **Collapsed** — no description: title + action row + close button all on one line
51997
+ *
51998
+ * Action buttons are **opt-in**: a button only renders when its label text
51999
+ * AND its callback are both present (`cancelText + onClose`, `submitText + onSubmit`).
52000
+ */
52001
+
52002
+ const AlertItem = ({
52003
+ id,
52004
+ variant,
52005
+ color,
52006
+ title,
52007
+ description,
52008
+ cancelText,
52009
+ submitText,
52010
+ size,
52011
+ withShadow,
52012
+ withBorder,
52013
+ inheritMainColor,
52014
+ animationDuration,
52015
+ position,
52016
+ onClose,
52017
+ onSubmit,
52018
+ onRemove,
52019
+ styleOverrides
52020
+ }) => {
52021
+ const theme = (0,react_namespaceObject.useTheme)();
52022
+ const tokens = getVariantTokens(theme, variant);
52023
+
52024
+ // ─── Color resolution ───────────────────────────────────────────────────────
52025
+
52026
+ // Resolve theme key → actual CSS color; fall back to raw CSS string
52027
+ const resolvedColor = color ? theme.colors[color] ?? color : undefined;
52028
+ const bg = resolvedColor ?? tokens.tintBg;
52029
+
52030
+ // When `color` is passed, auto-derive all derived colors; otherwise use variant tokens
52031
+ const dark = resolvedColor ? isColorDark(resolvedColor) : false;
52032
+ const textColor = resolvedColor ? getContrastColor(resolvedColor, theme.colors.greyDarker, theme.colors.white) : undefined;
52033
+ const iconColor = resolvedColor ? dark ? theme.colors.white : darkenColor(resolvedColor) : tokens.iconColor;
52034
+ const borderColor = resolvedColor ? darkenColor(resolvedColor) : tokens.accentColor;
52035
+ const closeIconColor = resolvedColor ? dark ? theme.colors.white : theme.colors.greyDarker60 : inheritMainColor ? tokens.accentColor : theme.colors.greyDarker60;
52036
+
52037
+ // styleOverrides can still pin specific colors on top of everything above
52038
+ const resolvedIconColor = styleOverrides?.iconColor ?? iconColor;
52039
+ const resolvedCloseIconColor = styleOverrides?.closeIconColor ?? closeIconColor;
52040
+
52041
+ // ─── Handlers ───────────────────────────────────────────────────────────────
52042
+
52043
+ const handleClose = () => {
52044
+ onRemove(id);
52045
+ onClose?.();
52046
+ };
52047
+ const handleSubmit = () => {
52048
+ onRemove(id);
52049
+ onSubmit?.();
52050
+ };
52051
+ const hasDescription = !!description;
52052
+
52053
+ // ─── Render ─────────────────────────────────────────────────────────────────
52054
+
52055
+ return (0,jsx_runtime_namespaceObject.jsxs)("div", {
52056
+ css: [itemWrapperStyles(bg, hasDescription), itemSizeStyles[size], itemAnimationStyles(position, animationDuration), withShadow && shadowStyles(theme), withBorder && borderStyles(borderColor), styleOverrides?.root, true ? "" : 0, true ? "" : 0],
52057
+ children: [(0,jsx_runtime_namespaceObject.jsx)("div", {
52058
+ css: [iconColStyles, styleOverrides?.icon, true ? "" : 0, true ? "" : 0],
52059
+ children: (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
52060
+ name: variantIcons[variant],
52061
+ color: resolvedIconColor,
52062
+ size: 24
52063
+ })
52064
+ }), hasDescription ? (0,jsx_runtime_namespaceObject.jsxs)("div", {
52065
+ css: contentColStyles,
52066
+ children: [(0,jsx_runtime_namespaceObject.jsxs)("div", {
52067
+ css: expandedHeaderRowStyles,
52068
+ children: [title && (0,jsx_runtime_namespaceObject.jsx)("span", {
52069
+ css: [titleTextStyles(theme, textColor), styleOverrides?.title, true ? "" : 0, true ? "" : 0],
52070
+ children: title
52071
+ }), (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
52072
+ name: "cross",
52073
+ size: 18,
52074
+ color: resolvedCloseIconColor,
52075
+ css: [closeBtnStyles, styleOverrides?.closeButton, true ? "" : 0, true ? "" : 0],
52076
+ onClick: handleClose,
52077
+ "aria-label": "Close alert"
52078
+ })]
52079
+ }), (0,jsx_runtime_namespaceObject.jsx)("p", {
52080
+ css: [descriptionStyles(theme, textColor), styleOverrides?.description, true ? "" : 0, true ? "" : 0],
52081
+ children: description
52082
+ }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
52083
+ css: [actionsRowStyles, styleOverrides?.actions, true ? "" : 0, true ? "" : 0],
52084
+ children: [cancelText && onClose && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52085
+ variant: "tertiary",
52086
+ css: [actionBtnStyles(theme, textColor), styleOverrides?.actionButton, true ? "" : 0, true ? "" : 0],
52087
+ onClick: handleClose,
52088
+ children: cancelText
52089
+ }), submitText && onSubmit && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52090
+ variant: "tertiary",
52091
+ css: [actionBtnStyles(theme, textColor), styleOverrides?.actionButton, true ? "" : 0, true ? "" : 0],
52092
+ onClick: handleSubmit,
52093
+ children: submitText
52094
+ })]
52095
+ })]
52096
+ }) : (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
52097
+ children: [title && (0,jsx_runtime_namespaceObject.jsx)("span", {
52098
+ css: [titleTextStyles(theme, textColor), collapsedTitleStyles, styleOverrides?.title, true ? "" : 0, true ? "" : 0],
52099
+ children: title
52100
+ }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
52101
+ css: [actionsRowStyles, styleOverrides?.actions, true ? "" : 0, true ? "" : 0],
52102
+ children: [cancelText && onClose && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52103
+ variant: "tertiary",
52104
+ css: [actionBtnStyles(theme, textColor), styleOverrides?.actionButton, true ? "" : 0, true ? "" : 0],
52105
+ onClick: handleClose,
52106
+ children: cancelText
52107
+ }), submitText && onSubmit && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52108
+ variant: "tertiary",
52109
+ css: [actionBtnStyles(theme, textColor), styleOverrides?.actionButton, true ? "" : 0, true ? "" : 0],
52110
+ onClick: handleSubmit,
52111
+ children: submitText
52112
+ })]
52113
+ }), (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
52114
+ name: "cross",
52115
+ size: 18,
52116
+ color: resolvedCloseIconColor,
52117
+ css: [closeBtnStyles, styleOverrides?.closeButton, true ? "" : 0, true ? "" : 0],
52118
+ onClick: handleClose,
52119
+ "aria-label": "Close alert"
52120
+ })]
52121
+ })]
52122
+ });
52123
+ };
52124
+ ;// ./src/components/NotificationComponents/Alert/Alert.tsx
52125
+
52126
+
52127
+
52128
+
52129
+
52130
+
52131
+
52132
+ let alertIdCounter = 0;
52133
+ const generateAlertId = () => `alert-${Date.now()}-${++alertIdCounter}`;
52134
+
52135
+ /**
52136
+ * Alert — portal-based notification container driven by the Observer pattern.
52137
+ *
52138
+ * ## Usage pattern (two steps)
52139
+ *
52140
+ * **Step 1 — mount once** near the app root (layout, shell, etc.).
52141
+ * The component renders nothing until `showAlert` is called.
52142
+ *
52143
+ * ```tsx
52144
+ * // app-root.tsx
52145
+ * import { Alert, NotificationPositions } from '@ssa-ui-kit/core';
52146
+ *
52147
+ * <Alert position={NotificationPositions.rightTop} withShadow maxAmount={5} />
52148
+ * ```
52149
+ *
52150
+ * **Step 2 — trigger from anywhere** via `showAlert`.
52151
+ * No refs, no prop drilling, no context required.
52152
+ *
52153
+ * ```tsx
52154
+ * import { showAlert, AlertVariants } from '@ssa-ui-kit/core';
52155
+ *
52156
+ * showAlert({ variant: AlertVariants.success, title: 'Saved!' });
52157
+ *
52158
+ * showAlert({
52159
+ * variant: AlertVariants.error,
52160
+ * title: 'Upload failed',
52161
+ * description: 'File exceeds 10 MB.',
52162
+ * cancelText: 'Dismiss',
52163
+ * onClose: () => {}, // required for the cancel button to appear
52164
+ * submitText: 'Retry',
52165
+ * onSubmit: () => retryFn(), // required for the submit button to appear
52166
+ * });
52167
+ * ```
52168
+ *
52169
+ * ## Key props
52170
+ * - `position` — one of `NotificationPositions` (default: `rightTop`)
52171
+ * - `maxAmount` — cap on simultaneous alerts; oldest is dropped when exceeded
52172
+ * - `withShadow` — drop shadow (default: `true`)
52173
+ * - `withBorder` — 1 px accent-color border (default: `false`)
52174
+ * - `inheritMainColor`— close icon matches the variant accent color
52175
+ * - `containerSelector` — CSS selector for a custom portal target; falls back to `document.body`
52176
+ * - `styles` — `AlertStyleOverrides` object for per-slot CSS customization without touching the theme
52177
+ *
52178
+ * ## Stacking order
52179
+ * - `*-top` positions → newest alert prepended (appears at the top of the stack)
52180
+ * - `*-bottom` positions → newest alert appended (appears at the bottom of the stack)
52181
+ *
52182
+ * When `maxAmount` is set and the stack is full, the oldest alert is dropped:
52183
+ * - `*-top`: drops from the **end** of the array (bottom of the stack)
52184
+ * - `*-bottom`: drops from the **start** of the array (top of the stack)
52185
+ *
52186
+ * @category Components
52187
+ * @subcategory Notification
52188
+ */
52189
+ const Alert = ({
52190
+ position = NotificationPositions.rightTop,
52191
+ size = NotificationSizes.small,
52192
+ withShadow = true,
52193
+ withBorder = false,
52194
+ inheritMainColor = false,
52195
+ cancelText = 'Cancel',
52196
+ submitText = 'Submit',
52197
+ containerSelector,
52198
+ animationDuration = 300,
52199
+ maxAmount,
52200
+ styles
52201
+ }) => {
52202
+ const [alerts, setAlerts] = (0,external_react_namespaceObject.useState)([]);
52203
+
52204
+ // Each Alert instance gets a unique subscription key via useId().
52205
+ // This allows multiple <Alert> components in the same document (e.g. Storybook
52206
+ // Docs view renders several stories simultaneously) to coexist without one
52207
+ // overwriting the other's observer subscription.
52208
+ const instanceId = (0,external_react_namespaceObject.useId)();
52209
+
52210
+ // useEffectEvent gives us a stable function reference that always reads the
52211
+ // latest prop values — no manual refs needed. The effect subscribes once and
52212
+ // never re-runs, while the handler always sees fresh position, maxAmount, etc.
52213
+ const handleDispatch = (0,external_react_namespaceObject.useEffectEvent)(params => {
52214
+ const newAlert = {
52215
+ ...params,
52216
+ id: generateAlertId()
52217
+ };
52218
+ setAlerts(prev => {
52219
+ const isTop = position.includes('top');
52220
+ // top-*: prepend so newest is at index 0 (top of the visual stack)
52221
+ // bottom-*: append so newest is at the end (bottom of the visual stack)
52222
+ const next = isTop ? [newAlert, ...prev] : [...prev, newAlert];
52223
+ if (maxAmount && next.length > maxAmount) {
52224
+ // top-*: oldest is at the end → keep the first `maxAmount` entries
52225
+ // bottom-*: oldest is at the start → keep the last `maxAmount` entries
52226
+ return isTop ? next.slice(0, maxAmount) : next.slice(-maxAmount);
52227
+ }
52228
+ return next;
52229
+ });
52230
+ });
52231
+ (0,external_react_namespaceObject.useEffect)(() => {
52232
+ alertObserver.subscribe(instanceId, handleDispatch);
52233
+ return () => alertObserver.unsubscribe(instanceId);
52234
+ }, []);
52235
+ const removeAlert = id => {
52236
+ setAlerts(prev => prev.filter(a => a.id !== id));
52237
+ };
52238
+ if (alerts.length === 0 || typeof document === 'undefined') {
52239
+ return null;
52240
+ }
52241
+ const container = containerSelector && document.querySelector(containerSelector) || document.body;
52242
+ return /*#__PURE__*/(0,external_react_dom_namespaceObject.createPortal)((0,jsx_runtime_namespaceObject.jsx)("div", {
52243
+ css: containerStyles(position),
52244
+ children: alerts.map(alert => (0,jsx_runtime_namespaceObject.jsx)(AlertItem, {
52245
+ size: alert.size ?? size,
52246
+ cancelText: alert.cancelText ?? cancelText,
52247
+ submitText: alert.submitText ?? submitText,
52248
+ withShadow: withShadow,
52249
+ withBorder: withBorder,
52250
+ inheritMainColor: inheritMainColor,
52251
+ animationDuration: animationDuration,
52252
+ position: position,
52253
+ styleOverrides: styles,
52254
+ id: alert.id,
52255
+ variant: alert.variant,
52256
+ color: alert.color,
52257
+ title: alert.title,
52258
+ description: alert.description,
52259
+ onClose: alert.onClose,
52260
+ onSubmit: alert.onSubmit,
52261
+ onRemove: removeAlert
52262
+ }, alert.id))
52263
+ }), container);
52264
+ };
52265
+ /* harmony default export */ const Alert_Alert = (Alert);
52266
+ ;// ./src/components/NotificationComponents/Toast/toastObserver.ts
52267
+ /**
52268
+ * Module-level singleton connecting `showToast()` calls to mounted `<Toast>`
52269
+ * components.
52270
+ *
52271
+ * Architecture:
52272
+ * ```
52273
+ * showToast(params)
52274
+ * └─► toastObserver.dispatch(params)
52275
+ * └─► Toast.tsx subscription callback (added in useEffect, removed on unmount)
52276
+ * └─► setToasts(prev => [...]) → React state update → portal
52277
+ * ```
52278
+ *
52279
+ * Multiple `<Toast>` instances are supported (each subscribes under a unique
52280
+ * `useId()` key). In a typical app you mount exactly one `<Toast>` near the root.
52281
+ */
52282
+
52283
+ const toastObserver = createObserver();
52284
+
52285
+ /**
52286
+ * Imperatively triggers a new toast inside every mounted `<Toast>` component.
52287
+ *
52288
+ * The `<Toast>` component must be mounted somewhere in the tree (typically near
52289
+ * the app root) for dispatched toasts to be displayed.
52290
+ *
52291
+ * @example
52292
+ * ```ts
52293
+ * import { showToast, ToastVariants } from '@ssa-ui-kit/core';
52294
+ *
52295
+ * // Simple
52296
+ * showToast({ variant: ToastVariants.default, title: 'File saved' });
52297
+ *
52298
+ * // With custom color + progress
52299
+ * showToast({ variant: ToastVariants.default, title: 'Uploading…', color: 'blue', withProgress: true });
52300
+ *
52301
+ * // Fully custom content (outer card + progress bar still render)
52302
+ * showToast({
52303
+ * variant: ToastVariants.default,
52304
+ * renderProp: (close) => <MyCard onDismiss={close} />,
52305
+ * });
52306
+ * ```
52307
+ */
52308
+ const showToast = params => {
52309
+ toastObserver.dispatch(params);
52310
+ };
52311
+ ;// ./src/components/NotificationComponents/hooks/useAutoDismiss.ts
52312
+
52313
+
52314
+ /**
52315
+ * Manages an auto-dismiss timer with hover-pause support.
52316
+ *
52317
+ * When `timeout` is `undefined` no timer is set and the item stays visible
52318
+ * until the consumer removes it manually.
52319
+ *
52320
+ * When the user hovers over the card the timer pauses and resumes from where
52321
+ * it left off when they move away — `remainingRef` tracks the elapsed time so
52322
+ * the full `timeout` is never restarted from scratch after a hover.
52323
+ *
52324
+ * @param timeout Auto-dismiss duration in ms. `undefined` = no auto-dismiss.
52325
+ * @param onDismiss Called once when the timer fires naturally (not on manual close).
52326
+ *
52327
+ * @returns
52328
+ * - `isPaused` — whether the timer is currently paused (hover active).
52329
+ * `ToastItem` uses this to pause the CSS progress bar animation.
52330
+ * - `handleMouseEnter` — attach to the card's `onMouseEnter`.
52331
+ * - `handleMouseLeave` — attach to the card's `onMouseLeave`.
52332
+ *
52333
+ * @example
52334
+ * ```tsx
52335
+ * const { isPaused, handleMouseEnter, handleMouseLeave } = useAutoDismiss(
52336
+ * timeout,
52337
+ * () => { onRemove(id); onClose?.(); },
52338
+ * );
52339
+ * ```
52340
+ */
52341
+ const useAutoDismiss = (timeout, onDismiss) => {
52342
+ const [isPaused, setIsPaused] = (0,external_react_namespaceObject.useState)(false);
52343
+ // Tracks remaining ms so hover-resume restarts from where it was, not from the full timeout
52344
+ const remainingRef = (0,external_react_namespaceObject.useRef)(timeout);
52345
+ const startTimeRef = (0,external_react_namespaceObject.useRef)(0);
52346
+ (0,external_react_namespaceObject.useEffect)(() => {
52347
+ if (!timeout || isPaused) return;
52348
+ startTimeRef.current = Date.now();
52349
+ const timer = setTimeout(onDismiss, remainingRef.current);
52350
+ return () => clearTimeout(timer);
52351
+ }, [isPaused]);
52352
+ const handleMouseEnter = () => {
52353
+ if (!timeout) return;
52354
+ const elapsed = Date.now() - startTimeRef.current;
52355
+ remainingRef.current = Math.max(0, (remainingRef.current ?? timeout) - elapsed);
52356
+ setIsPaused(true);
52357
+ };
52358
+ const handleMouseLeave = () => setIsPaused(false);
52359
+ return {
52360
+ isPaused,
52361
+ handleMouseEnter,
52362
+ handleMouseLeave
52363
+ };
52364
+ };
52365
+ ;// ./src/components/NotificationComponents/Toast/types.ts
52366
+ let ToastVariants = /*#__PURE__*/function (ToastVariants) {
52367
+ ToastVariants["secondary"] = "secondary";
52368
+ ToastVariants["neutral"] = "neutral";
52369
+ ToastVariants["dark"] = "dark";
52370
+ return ToastVariants;
52371
+ }({});
52372
+
52373
+ /**
52374
+ * Component-level props for the `<Toast>` portal container.
52375
+ *
52376
+ * Mount once near the app root; call `showToast(...)` from anywhere.
52377
+ */
52378
+
52379
+ /**
52380
+ * Parameters for an individual toast triggered via `showToast(params)`.
52381
+ *
52382
+ * Only `variant` is required. Everything else is optional.
52383
+ *
52384
+ * @example
52385
+ * ```ts
52386
+ * // Minimal
52387
+ * showToast({ variant: ToastVariants.default, title: 'Saved!' });
52388
+ *
52389
+ * // With color, custom timeout, and progress bar
52390
+ * showToast({
52391
+ * variant: ToastVariants.default,
52392
+ * title: 'Uploading…',
52393
+ * color: 'purple',
52394
+ * timeout: 8000,
52395
+ * withProgress: true,
52396
+ * });
52397
+ *
52398
+ * // Fully custom content
52399
+ * showToast({
52400
+ * variant: ToastVariants.default,
52401
+ * renderProp: (close) => <MyCustomCard onDismiss={close} />,
52402
+ * });
52403
+ * ```
52404
+ */
52405
+ ;// ./src/components/NotificationComponents/Toast/styles.ts
52406
+ function Toast_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
52407
+
52408
+
52409
+ // ─── Re-export shared structural styles ──────────────────────────────────────
52410
+
52411
+
52412
+
52413
+ // ─── Variant color tokens ────────────────────────────────────────────────────
52414
+
52415
+ const styles_getVariantTokens = (theme, variant) => {
52416
+ const map = {
52417
+ [ToastVariants.secondary]: {
52418
+ bg: theme.palette.secondary.light,
52419
+ iconColor: theme.colors.greyDarker60,
52420
+ borderColor: theme.colors.greyFocused,
52421
+ textColor: theme.colors.greyDarker
52422
+ },
52423
+ [ToastVariants.neutral]: {
52424
+ bg: theme.colors.white,
52425
+ iconColor: theme.colors.greyDarker60,
52426
+ borderColor: theme.colors.greyOutline,
52427
+ textColor: theme.colors.greyDarker
52428
+ },
52429
+ [ToastVariants.dark]: {
52430
+ bg: theme.colors.greyBackground,
52431
+ iconColor: theme.colors.white,
52432
+ borderColor: theme.colors.greyBackground,
52433
+ textColor: theme.colors.white
52434
+ }
52435
+ };
52436
+ return map[variant];
52437
+ };
52438
+
52439
+ // ─── Toast item wrapper ───────────────────────────────────────────────────────
52440
+ // Unlike Alert (which layers a rgba tint over white via backgroundImage),
52441
+ // Toast backgrounds are always fully opaque — variant tokens and custom `color`
52442
+ // values are solid colors. The wrapper uses `overflow: hidden` so the progress
52443
+ // bar is clipped to the card's border-radius.
52444
+
52445
+ const styles_itemWrapperStyles = (background, hasDescription) => /*#__PURE__*/(0,react_namespaceObject.css)("position:relative;border-radius:8px;padding:12px 16px;display:flex;gap:10px;box-sizing:border-box;background:", background, ";align-items:", hasDescription ? 'flex-start' : 'center', ";overflow:hidden;" + ( true ? "" : 0), true ? "" : 0);
52446
+
52447
+ // ─── Text styles (accept explicit color to support `color` prop contrast) ────
52448
+
52449
+ const styles_titleTextStyles = textColor => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:14px;font-weight:600;line-height:20px;color:", textColor, ";word-break:break-word;" + ( true ? "" : 0), true ? "" : 0);
52450
+ const styles_descriptionStyles = textColor => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:13px;font-weight:400;line-height:18px;color:", textColor, ";opacity:0.8;margin:0;word-break:break-word;" + ( true ? "" : 0), true ? "" : 0);
52451
+ const styles_actionBtnStyles = textColor => /*#__PURE__*/(0,react_namespaceObject.css)("background:none;border:none;padding:0;cursor:pointer;font-size:13px;font-weight:500;line-height:18px;height:max-content;letter-spacing:0.2px;color:", textColor, ";opacity:0.75;&:hover{opacity:1;}" + ( true ? "" : 0), true ? "" : 0);
52452
+
52453
+ // ─── Progress bar ─────────────────────────────────────────────────────────────
52454
+ // The bar is absolutely positioned at the bottom of the card (overflow: hidden
52455
+ // on the wrapper clips it to the card's border-radius).
52456
+ //
52457
+ // `animation-play-state` is controlled via inline `style` prop (not a class)
52458
+ // so the animation resumes from its paused position when hover ends — changing
52459
+ // an Emotion-generated class would restart the animation from 0%.
52460
+
52461
+ const drainProgress = (0,react_namespaceObject.keyframes)`
52462
+ from { width: 100%; }
52463
+ to { width: 0%; }
52464
+ `;
52465
+ const progressBarContainerStyles = true ? {
52466
+ name: "1cq5vou",
52467
+ styles: "position:absolute;bottom:0;left:0;right:0;height:3px;background:transparent"
52468
+ } : 0;
52469
+
52470
+ /**
52471
+ * Returns the base progress-bar styles including the drain animation.
52472
+ * Pass `animation-play-state` separately via an inline `style` prop so the
52473
+ * animation can be paused/resumed without resetting.
52474
+ */
52475
+ const progressBarStyles = (theme, timeout, color) => /*#__PURE__*/(0,react_namespaceObject.css)("height:100%;width:100%;background:", theme.colors[color] ?? color, ";animation:", drainProgress, " ", timeout, "ms linear forwards;border-radius:0 0 8px 8px;" + ( true ? "" : 0), true ? "" : 0);
52476
+ ;// ./src/components/NotificationComponents/Toast/ToastItem.tsx
52477
+ /**
52478
+ * @internal
52479
+ *
52480
+ * Renders a single toast card inside the `<Toast>` portal container.
52481
+ * Not part of the public API — consumers call `showToast(params)` to trigger
52482
+ * toasts and configure appearance via `<Toast>` component props.
52483
+ *
52484
+ * Compared to `AlertItem`, `ToastItem` adds:
52485
+ * - **Auto-dismiss timer** with hover-pause support
52486
+ * - **Progress bar** (`withProgress`) that drains over the toast lifetime
52487
+ * - **`color` prop** driving background, text, icon, and border colors
52488
+ * - **`renderProp`** for fully custom inner content
52489
+ */
52490
+
52491
+
52492
+
52493
+
52494
+
52495
+
52496
+
52497
+
52498
+
52499
+ // Default timeout used by the component (4 s) — defined in Toast.tsx as prop
52500
+ // default, but ToastItem also needs to know it has a timer active.
52501
+
52502
+ const ToastItem = ({
52503
+ id,
52504
+ variant,
52505
+ color,
52506
+ title,
52507
+ description,
52508
+ cancelText,
52509
+ submitText,
52510
+ size,
52511
+ withShadow,
52512
+ withBorder,
52513
+ animationDuration,
52514
+ position,
52515
+ timeout,
52516
+ withProgress = false,
52517
+ progressColor,
52518
+ onClose,
52519
+ onSubmit,
52520
+ onRemove,
52521
+ renderProp
52522
+ }) => {
52523
+ const theme = (0,react_namespaceObject.useTheme)();
52524
+ const tokens = styles_getVariantTokens(theme, variant);
52525
+
52526
+ // ─── Color resolution ───────────────────────────────────────────────────────
52527
+
52528
+ // Resolve theme key → actual color value; fall back to raw CSS string
52529
+ const resolvedColor = color ? theme.colors[color] ?? color : undefined;
52530
+ const bg = resolvedColor ?? tokens.bg;
52531
+ const dark = resolvedColor ? isColorDark(resolvedColor) : variant === ToastVariants.dark;
52532
+ const textColor = resolvedColor ? getContrastColor(resolvedColor, theme.colors.greyDarker, theme.colors.white) : tokens.textColor;
52533
+
52534
+ // On light bg: darken the color for icon/border; on dark bg: use contrast (white)
52535
+ const accentColor = resolvedColor ? dark ? theme.colors.white : darkenColor(resolvedColor) : tokens.iconColor;
52536
+ const borderColor = resolvedColor ? darkenColor(resolvedColor) : tokens.borderColor;
52537
+
52538
+ // Precedence: explicit progressColor prop → auto darkened `color` → blueNotification
52539
+ const resolvedProgressColor = progressColor ?? (resolvedColor ? darkenColor(resolvedColor) : theme.colors.blueNotification);
52540
+ const resolvedCloseIconColor = dark ? theme.colors.white : theme.colors.greyDarker60;
52541
+
52542
+ // ─── Auto-dismiss with hover-pause ──────────────────────────────────────────
52543
+
52544
+ const {
52545
+ isPaused,
52546
+ handleMouseEnter,
52547
+ handleMouseLeave
52548
+ } = useAutoDismiss(timeout, () => {
52549
+ onRemove(id);
52550
+ onClose?.();
52551
+ });
52552
+
52553
+ // ─── Dismiss helpers ────────────────────────────────────────────────────────
52554
+
52555
+ const handleClose = () => {
52556
+ onRemove(id);
52557
+ onClose?.();
52558
+ };
52559
+ const handleSubmit = () => {
52560
+ onRemove(id);
52561
+ onSubmit?.();
52562
+ };
52563
+
52564
+ // ─── Derived layout flags ───────────────────────────────────────────────────
52565
+
52566
+ const hasDescription = !!description;
52567
+ const showProgress = withProgress && !!timeout;
52568
+
52569
+ // ─── Render ─────────────────────────────────────────────────────────────────
52570
+
52571
+ return (0,jsx_runtime_namespaceObject.jsxs)("div", {
52572
+ css: [styles_itemWrapperStyles(bg, hasDescription), itemSizeStyles[size], itemAnimationStyles(position, animationDuration), withShadow && shadowStyles(theme), withBorder && borderStyles(borderColor), true ? "" : 0, true ? "" : 0],
52573
+ onMouseEnter: handleMouseEnter,
52574
+ onMouseLeave: handleMouseLeave,
52575
+ children: [renderProp ? renderProp(handleClose) : (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
52576
+ children: [(0,jsx_runtime_namespaceObject.jsx)("div", {
52577
+ css: iconColStyles,
52578
+ children: (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
52579
+ name: "check-circle",
52580
+ color: accentColor,
52581
+ size: 24
52582
+ })
52583
+ }), hasDescription ? (0,jsx_runtime_namespaceObject.jsxs)("div", {
52584
+ css: contentColStyles,
52585
+ children: [(0,jsx_runtime_namespaceObject.jsxs)("div", {
52586
+ css: expandedHeaderRowStyles,
52587
+ children: [title && (0,jsx_runtime_namespaceObject.jsx)("span", {
52588
+ css: styles_titleTextStyles(textColor),
52589
+ children: title
52590
+ }), (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
52591
+ name: "cross",
52592
+ size: 18,
52593
+ onClick: handleClose,
52594
+ "aria-label": "Close toast",
52595
+ color: resolvedCloseIconColor,
52596
+ css: closeBtnStyles
52597
+ })]
52598
+ }), (0,jsx_runtime_namespaceObject.jsx)("p", {
52599
+ css: styles_descriptionStyles(textColor),
52600
+ children: description
52601
+ }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
52602
+ css: actionsRowStyles,
52603
+ children: [cancelText && onClose && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52604
+ variant: "tertiary",
52605
+ css: styles_actionBtnStyles(textColor),
52606
+ onClick: handleClose,
52607
+ children: cancelText
52608
+ }), submitText && onSubmit && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52609
+ variant: "tertiary",
52610
+ css: styles_actionBtnStyles(textColor),
52611
+ onClick: handleSubmit,
52612
+ children: submitText
52613
+ })]
52614
+ })]
52615
+ }) : (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
52616
+ children: [title && (0,jsx_runtime_namespaceObject.jsx)("span", {
52617
+ css: [styles_titleTextStyles(textColor), collapsedTitleStyles, true ? "" : 0, true ? "" : 0],
52618
+ children: title
52619
+ }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
52620
+ css: actionsRowStyles,
52621
+ children: [cancelText && onClose && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52622
+ variant: "tertiary",
52623
+ css: styles_actionBtnStyles(textColor),
52624
+ onClick: handleClose,
52625
+ children: cancelText
52626
+ }), submitText && onSubmit && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
52627
+ variant: "tertiary",
52628
+ css: styles_actionBtnStyles(textColor),
52629
+ onClick: handleSubmit,
52630
+ children: submitText
52631
+ })]
52632
+ }), (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
52633
+ name: "cross",
52634
+ size: 18,
52635
+ onClick: handleClose,
52636
+ "aria-label": "Close toast",
52637
+ color: resolvedCloseIconColor,
52638
+ css: closeBtnStyles
52639
+ })]
52640
+ })]
52641
+ }), showProgress && (0,jsx_runtime_namespaceObject.jsx)("div", {
52642
+ css: progressBarContainerStyles,
52643
+ children: (0,jsx_runtime_namespaceObject.jsx)("div", {
52644
+ css: progressBarStyles(theme, timeout, resolvedProgressColor)
52645
+ // Inline style for play-state so the animation resumes from its
52646
+ // current position on hover-resume — changing an Emotion class
52647
+ // would restart the animation from 0%.
52648
+ ,
52649
+ style: {
52650
+ animationPlayState: isPaused ? 'paused' : 'running'
52651
+ }
52652
+ })
52653
+ })]
52654
+ });
52655
+ };
52656
+ ;// ./src/components/NotificationComponents/Toast/Toast.tsx
52657
+
52658
+
52659
+
52660
+
52661
+
52662
+
52663
+
52664
+
52665
+ /**
52666
+ * Toast — portal-based notification container driven by the Observer pattern.
52667
+ *
52668
+ * ## Usage pattern (two steps)
52669
+ *
52670
+ * **Step 1 — mount once** near the app root:
52671
+ * ```tsx
52672
+ * import { Toast, NotificationPositions } from '@ssa-ui-kit/core';
52673
+ *
52674
+ * <Toast position={NotificationPositions.rightBottom} withShadow timeout={4000} />
52675
+ * ```
52676
+ *
52677
+ * **Step 2 — trigger from anywhere** via `showToast`:
52678
+ * ```tsx
52679
+ * import { showToast, ToastVariants } from '@ssa-ui-kit/core';
52680
+ *
52681
+ * showToast({ variant: ToastVariants.default, title: 'File saved!' });
52682
+ *
52683
+ * showToast({
52684
+ * variant: ToastVariants.default,
52685
+ * title: 'Uploading…',
52686
+ * color: 'blue',
52687
+ * timeout: 8000,
52688
+ * withProgress: true,
52689
+ * });
52690
+ * ```
52691
+ *
52692
+ * ## Key props
52693
+ * - `position` — one of `NotificationPositions` (default: `rightBottom`)
52694
+ * - `timeout` — default auto-dismiss ms; `undefined` = no auto-dismiss (default: `4000`)
52695
+ * - `withProgress` — show progress bar by default (default: `false`)
52696
+ * - `maxAmount` — cap on simultaneous toasts; oldest dropped when exceeded
52697
+ * - `withShadow` — drop shadow (default: `true`)
52698
+ * - `withBorder` — 1 px border (default: `false`)
52699
+ * - `containerSelector`— CSS selector for a custom portal target
52700
+ *
52701
+ * ## Stacking order
52702
+ * - `*-top` positions → newest toast prepended (top of stack)
52703
+ * - `*-bottom` positions → newest toast appended (bottom of stack)
52704
+ *
52705
+ * ## `timeout` precedence
52706
+ * Per-toast `timeout` from `showToast` overrides the component-level default.
52707
+ * `undefined` at either level disables auto-dismiss for that toast.
52708
+ *
52709
+ * @category Components
52710
+ * @subcategory Notification
52711
+ */
52712
+
52713
+ let toastIdCounter = 0;
52714
+ const generateToastId = () => `toast-${Date.now()}-${++toastIdCounter}`;
52715
+ const Toast = props => {
52716
+ const {
52717
+ position = NotificationPositions.rightBottom,
52718
+ size = NotificationSizes.small,
52719
+ withShadow = true,
52720
+ withBorder = false,
52721
+ cancelText,
52722
+ submitText,
52723
+ containerSelector,
52724
+ animationDuration = 300,
52725
+ maxAmount,
52726
+ withProgress = false,
52727
+ progressColor
52728
+ } = props;
52729
+
52730
+ // Check the raw props object so that `<Toast timeout={undefined} />` (explicitly
52731
+ // disabling auto-dismiss) is distinguished from `<Toast />` (use the 4 s default).
52732
+ // Destructuring defaults cannot make this distinction — both give `timeout = 4000`.
52733
+ const timeout = 'timeout' in props ? props.timeout : 4000;
52734
+ const [toasts, setToasts] = (0,external_react_namespaceObject.useState)([]);
52735
+
52736
+ // Unique per-instance key — multiple <Toast> mounts (e.g. Storybook Docs view)
52737
+ // each keep independent subscriptions without overwriting each other.
52738
+ const instanceId = (0,external_react_namespaceObject.useId)();
52739
+
52740
+ // useEffectEvent gives us a stable function reference that always reads the
52741
+ // latest prop values — no manual refs needed. The effect subscribes once and
52742
+ // never re-runs, while the handler always sees fresh position, maxAmount, etc.
52743
+ const handleDispatch = (0,external_react_namespaceObject.useEffectEvent)(params => {
52744
+ const resolvedTimeout = 'timeout' in params ? params.timeout : timeout;
52745
+ const resolvedWithProgress = !!resolvedTimeout && ('withProgress' in params ? !!params.withProgress : withProgress);
52746
+ const resolvedProgressColor = params.progressColor ?? progressColor;
52747
+ const newToast = {
52748
+ ...params,
52749
+ id: generateToastId(),
52750
+ resolvedTimeout,
52751
+ resolvedWithProgress,
52752
+ resolvedProgressColor
52753
+ };
52754
+ setToasts(prev => {
52755
+ const isTop = position.includes('top');
52756
+ const next = isTop ? [newToast, ...prev] : [...prev, newToast];
52757
+ if (maxAmount && next.length > maxAmount) {
52758
+ return isTop ? next.slice(0, maxAmount) : next.slice(-maxAmount);
52759
+ }
52760
+ return next;
52761
+ });
52762
+ });
52763
+ (0,external_react_namespaceObject.useEffect)(() => {
52764
+ toastObserver.subscribe(instanceId, handleDispatch);
52765
+ return () => toastObserver.unsubscribe(instanceId);
52766
+ }, []);
52767
+ const removeToast = id => {
52768
+ setToasts(prev => prev.filter(t => t.id !== id));
52769
+ };
52770
+ if (toasts.length === 0 || typeof document === 'undefined') {
52771
+ return null;
52772
+ }
52773
+ const container = containerSelector && document.querySelector(containerSelector) || document.body;
52774
+ return /*#__PURE__*/(0,external_react_dom_namespaceObject.createPortal)((0,jsx_runtime_namespaceObject.jsx)("div", {
52775
+ css: containerStyles(position),
52776
+ children: toasts.map(toast => (0,jsx_runtime_namespaceObject.jsx)(ToastItem, {
52777
+ ...toast,
52778
+ variant: toast.variant ?? ToastVariants.secondary,
52779
+ size: toast.size ?? size,
52780
+ cancelText: toast.cancelText ?? cancelText,
52781
+ submitText: toast.submitText ?? submitText,
52782
+ withShadow: withShadow,
52783
+ withBorder: withBorder,
52784
+ animationDuration: animationDuration,
52785
+ position: position,
52786
+ timeout: toast.resolvedTimeout,
52787
+ withProgress: toast.resolvedWithProgress,
52788
+ progressColor: toast.resolvedProgressColor,
52789
+ onRemove: removeToast
52790
+ }, toast.id))
52791
+ }), container);
52792
+ };
52793
+ /* harmony default export */ const Toast_Toast = (Toast);
52794
+ ;// ./src/components/NotificationComponents/Notification/notificationObserver.ts
52795
+ /**
52796
+ * Module-level singleton connecting imperative `showNotification()` calls to
52797
+ * whichever `<Notification>` components are currently mounted in the document.
52798
+ *
52799
+ * Architecture:
52800
+ *
52801
+ * showNotification(params)
52802
+ * └─▶ notificationObserver.dispatch(params)
52803
+ * └─▶ forEach subscriber (one per mounted <Notification>):
52804
+ * subscriber(params) ← registered in useEffect on mount
52805
+ * └─▶ setNotifications(prev => [...prev, newItem])
52806
+ * └─▶ React re-renders → NotificationItem appears in portal
52807
+ *
52808
+ * Multiple `<Notification>` instances can coexist (e.g. Storybook Docs view).
52809
+ * Each subscribes under a unique `useId()` key so dispatches reach all of them.
52810
+ */
52811
+
52812
+
52813
+ const notificationObserver = createObserver();
52814
+ const showNotification = params => {
52815
+ notificationObserver.dispatch(params);
52816
+ };
52817
+ ;// ./src/components/NotificationComponents/Notification/types.ts
52818
+ let NotificationVariants = /*#__PURE__*/function (NotificationVariants) {
52819
+ NotificationVariants["secondary"] = "secondary";
52820
+ NotificationVariants["neutral"] = "neutral";
52821
+ NotificationVariants["dark"] = "dark";
52822
+ return NotificationVariants;
52823
+ }({});
52824
+
52825
+ /**
52826
+ * Per-slot style overrides for `<Notification>`.
52827
+ *
52828
+ * Cards render inside a portal, so a top-level `css` prop cannot reach inner
52829
+ * elements. Use this object to customize any part of the card without touching
52830
+ * the global theme.
52831
+ *
52832
+ * @example
52833
+ * ```tsx
52834
+ * <Notification
52835
+ * styles={{
52836
+ * root: { borderRadius: 4 },
52837
+ * title: { fontWeight: 700 },
52838
+ * date: { color: '#9ca3af' },
52839
+ * }}
52840
+ * />
52841
+ * ```
52842
+ */
52843
+
52844
+ /**
52845
+ * Component-level props for the `<Notification>` portal container.
52846
+ *
52847
+ * Mount once near the app root; call `showNotification(...)` from anywhere.
52848
+ */
52849
+
52850
+ /**
52851
+ * Parameters for an individual notification triggered via `showNotification(params)`.
52852
+ *
52853
+ * Only `variant` is required. Everything else is optional.
52854
+ *
52855
+ * @example
52856
+ * ```ts
52857
+ * // Minimal
52858
+ * showNotification({ variant: NotificationVariants.default, title: 'John Doe' });
52859
+ *
52860
+ * // With avatar and timestamp
52861
+ * showNotification({
52862
+ * variant: NotificationVariants.neutral,
52863
+ * title: 'Jane Smith',
52864
+ * date: '5 minutes ago',
52865
+ * description: 'Left a comment on your post.',
52866
+ * icon: <img src={avatarUrl} alt="Jane" />,
52867
+ * });
52868
+ *
52869
+ * // With custom color
52870
+ * showNotification({
52871
+ * variant: NotificationVariants.default,
52872
+ * title: 'System',
52873
+ * color: 'purple',
52874
+ * date: 'Just now',
52875
+ * });
52876
+ * ```
52877
+ */
52878
+ ;// ./src/components/NotificationComponents/Notification/styles.ts
52879
+ function Notification_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
52880
+
52881
+
52882
+ // ─── Re-export shared structural styles ──────────────────────────────────────
52883
+
52884
+
52885
+
52886
+
52887
+ // ─── Variant color tokens ─────────────────────────────────────────────────────
52888
+
52889
+ const Notification_styles_getVariantTokens = (theme, variant) => {
52890
+ const map = {
52891
+ [NotificationVariants.secondary]: {
52892
+ bg: theme.palette.secondary.light,
52893
+ iconColor: theme.colors.greyDarker60,
52894
+ borderColor: theme.colors.greyFocused,
52895
+ textColor: theme.colors.greyDarker
52896
+ },
52897
+ [NotificationVariants.neutral]: {
52898
+ bg: theme.colors.white,
52899
+ iconColor: theme.colors.greyDarker60,
52900
+ borderColor: theme.colors.greyOutline,
52901
+ textColor: theme.colors.greyDarker
52902
+ },
52903
+ [NotificationVariants.dark]: {
52904
+ bg: theme.colors.greyBackground,
52905
+ iconColor: theme.colors.white,
52906
+ borderColor: theme.colors.greyBackground,
52907
+ textColor: theme.colors.white
52908
+ }
52909
+ };
52910
+ return map[variant];
52911
+ };
52912
+
52913
+ // ─── Item wrapper ─────────────────────────────────────────────────────────────
52914
+
52915
+ const Notification_styles_itemWrapperStyles = (background, hasDescription) => /*#__PURE__*/(0,react_namespaceObject.css)("position:relative;border-radius:8px;padding:12px 16px;display:flex;gap:12px;box-sizing:border-box;background:", background, ";align-items:", hasDescription ? 'flex-start' : 'center', ";" + ( true ? "" : 0), true ? "" : 0);
52916
+
52917
+ // ─── Icon column ──────────────────────────────────────────────────────────────
52918
+ // Larger than Alert/Toast (avatar-sized) and vertically centred with the
52919
+ // header row regardless of whether a description is present.
52920
+
52921
+ const styles_iconColStyles = true ? {
52922
+ name: "g2yk48",
52923
+ styles: "display:flex;flex-shrink:0;align-items:flex-start;padding-top:2px"
52924
+ } : 0;
52925
+
52926
+ // ─── Content column ───────────────────────────────────────────────────────────
52927
+
52928
+ const styles_contentColStyles = true ? {
52929
+ name: "1fllwkr",
52930
+ styles: "display:flex;flex-direction:column;gap:6px;flex:1;min-width:0"
52931
+ } : 0;
52932
+
52933
+ // ─── Header row: title · date · close ─────────────────────────────────────────
52934
+
52935
+ const headerRowStyles = true ? {
52936
+ name: "1yydxi7",
52937
+ styles: "display:flex;align-items:center;gap:8px"
52938
+ } : 0;
52939
+
52940
+ // ─── Text styles ──────────────────────────────────────────────────────────────
52941
+
52942
+ const Notification_styles_titleTextStyles = textColor => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:14px;font-weight:600;line-height:20px;color:", textColor, ";flex:1;min-width:0;word-break:break-word;" + ( true ? "" : 0), true ? "" : 0);
52943
+ const dateTextStyles = (theme, textColor) => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:12px;font-weight:400;line-height:18px;color:", textColor ?? theme.colors.greyDarker60, ";white-space:nowrap;flex-shrink:0;" + ( true ? "" : 0), true ? "" : 0);
52944
+ const Notification_styles_descriptionStyles = textColor => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:13px;font-weight:400;line-height:18px;color:", textColor, ";opacity:0.8;margin:0;word-break:break-word;" + ( true ? "" : 0), true ? "" : 0);
52945
+ const Notification_styles_actionBtnStyles = textColor => /*#__PURE__*/(0,react_namespaceObject.css)("background:none;border:none;padding:0;cursor:pointer;font-size:13px;font-weight:500;line-height:18px;height:max-content;letter-spacing:0.2px;color:", textColor, ";opacity:0.75;&:hover{opacity:1;}" + ( true ? "" : 0), true ? "" : 0);
52946
+ ;// ./src/components/NotificationComponents/Notification/NotificationItem.tsx
52947
+ /**
52948
+ * @internal
52949
+ *
52950
+ * Renders a single notification card inside the `<Notification>` portal container.
52951
+ * Not part of the public API — consumers call `showNotification(params)` to trigger
52952
+ * notifications and configure appearance via `<Notification>` component props.
52953
+ *
52954
+ * Layout:
52955
+ * ```
52956
+ * [icon] Title date [×]
52957
+ * Description text here.
52958
+ * Cancel Submit
52959
+ * ```
52960
+ *
52961
+ * The icon column accepts:
52962
+ * - A named icon string → rendered via `<Icon>`
52963
+ * - Any ReactNode (img, Avatar, etc.) → rendered as-is
52964
+ * - Omitted → falls back to `"user"` icon
52965
+ *
52966
+ * Action buttons are opt-in: a button only renders when both its label text
52967
+ * AND its callback are present (`cancelText + onClose`, `submitText + onSubmit`).
52968
+ */
52969
+
52970
+
52971
+
52972
+
52973
+
52974
+
52975
+
52976
+
52977
+
52978
+
52979
+ const NotificationItem = ({
52980
+ id,
52981
+ variant,
52982
+ color,
52983
+ date,
52984
+ icon,
52985
+ title,
52986
+ description,
52987
+ cancelText,
52988
+ submitText,
52989
+ size,
52990
+ withShadow,
52991
+ withBorder,
52992
+ animationDuration,
52993
+ position,
52994
+ timeout,
52995
+ onClose,
52996
+ onSubmit,
52997
+ onRemove,
52998
+ styleOverrides
52999
+ }) => {
53000
+ const theme = (0,react_namespaceObject.useTheme)();
53001
+ const tokens = Notification_styles_getVariantTokens(theme, variant);
53002
+
53003
+ // ─── Color resolution ───────────────────────────────────────────────────────
53004
+
53005
+ const resolvedColor = color ? theme.colors[color] ?? color : undefined;
53006
+ const bg = resolvedColor ?? tokens.bg;
53007
+ const dark = resolvedColor ? isColorDark(resolvedColor) : variant === NotificationVariants.dark;
53008
+ const textColor = resolvedColor ? getContrastColor(resolvedColor, theme.colors.greyDarker, theme.colors.white) : tokens.textColor;
53009
+ const iconColor = resolvedColor ? dark ? theme.colors.white : darkenColor(resolvedColor) : tokens.iconColor;
53010
+ const borderColor = resolvedColor ? darkenColor(resolvedColor) : tokens.borderColor;
53011
+ const closeIconColor = dark ? theme.colors.white : theme.colors.greyDarker60;
53012
+ const resolvedIconColor = styleOverrides?.iconColor ?? iconColor;
53013
+ const resolvedCloseIconColor = styleOverrides?.closeIconColor ?? closeIconColor;
53014
+
53015
+ // ─── Date text color — muted relative to the title ────────────────────────
53016
+ // On dark backgrounds (variant dark or custom dark color) use semi-transparent
53017
+ // white so the date is readable but visually secondary to the title.
53018
+ // On light backgrounds fall through to greyDarker60 inside dateTextStyles.
53019
+ const dateMutedColor = dark ? 'rgba(255, 255, 255, 0.6)' : undefined;
53020
+
53021
+ // ─── Auto-dismiss timer (optional) ──────────────────────────────────────────
53022
+
53023
+ const {
53024
+ handleMouseEnter,
53025
+ handleMouseLeave
53026
+ } = useAutoDismiss(timeout, () => {
53027
+ onRemove(id);
53028
+ onClose?.();
53029
+ });
53030
+
53031
+ // ─── Dismiss helpers ────────────────────────────────────────────────────────
53032
+
53033
+ const handleClose = () => {
53034
+ onRemove(id);
53035
+ onClose?.();
53036
+ };
53037
+ const handleSubmit = () => {
53038
+ onRemove(id);
53039
+ onSubmit?.();
53040
+ };
53041
+
53042
+ // ─── Icon element ────────────────────────────────────────────────────────────
53043
+
53044
+ const iconSize = size === NotificationSizes.large ? 46 : 36;
53045
+ const iconElement = typeof icon === 'string' ? (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
53046
+ name: icon,
53047
+ color: resolvedIconColor,
53048
+ size: iconSize
53049
+ }) : icon != null ? icon : (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
53050
+ name: "user",
53051
+ color: resolvedIconColor,
53052
+ size: iconSize
53053
+ });
53054
+
53055
+ // ─── Derived flags ───────────────────────────────────────────────────────────
53056
+
53057
+ const hasDescription = !!description;
53058
+
53059
+ // ─── Render ─────────────────────────────────────────────────────────────────
53060
+
53061
+ return (0,jsx_runtime_namespaceObject.jsxs)("div", {
53062
+ css: [Notification_styles_itemWrapperStyles(bg, hasDescription), itemSizeStyles[size], itemAnimationStyles(position, animationDuration), withShadow && shadowStyles(theme), withBorder && borderStyles(borderColor), styleOverrides?.root, true ? "" : 0, true ? "" : 0],
53063
+ onMouseEnter: handleMouseEnter,
53064
+ onMouseLeave: handleMouseLeave,
53065
+ children: [(0,jsx_runtime_namespaceObject.jsx)("div", {
53066
+ css: [styles_iconColStyles, styleOverrides?.icon, true ? "" : 0, true ? "" : 0],
53067
+ children: iconElement
53068
+ }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
53069
+ css: styles_contentColStyles,
53070
+ children: [(0,jsx_runtime_namespaceObject.jsxs)("div", {
53071
+ css: headerRowStyles,
53072
+ children: [title && (0,jsx_runtime_namespaceObject.jsx)("span", {
53073
+ css: [Notification_styles_titleTextStyles(textColor), styleOverrides?.title, true ? "" : 0, true ? "" : 0],
53074
+ children: title
53075
+ }), date && (0,jsx_runtime_namespaceObject.jsx)("span", {
53076
+ css: [dateTextStyles(theme, dateMutedColor), styleOverrides?.date, true ? "" : 0, true ? "" : 0],
53077
+ children: date
53078
+ }), (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
53079
+ name: "cross",
53080
+ css: [closeBtnStyles, styleOverrides?.closeButton, true ? "" : 0, true ? "" : 0],
53081
+ onClick: handleClose,
53082
+ "aria-label": "Close notification",
53083
+ size: 18,
53084
+ color: resolvedCloseIconColor
53085
+ })]
53086
+ }), hasDescription && (0,jsx_runtime_namespaceObject.jsx)("p", {
53087
+ css: [Notification_styles_descriptionStyles(textColor), styleOverrides?.description, true ? "" : 0, true ? "" : 0],
53088
+ children: description
53089
+ }), (cancelText && onClose || submitText && onSubmit) && (0,jsx_runtime_namespaceObject.jsxs)("div", {
53090
+ css: [actionsRowStyles, styleOverrides?.actions, true ? "" : 0, true ? "" : 0],
53091
+ children: [cancelText && onClose && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
53092
+ variant: "tertiary",
53093
+ css: [Notification_styles_actionBtnStyles(textColor), styleOverrides?.actionButton, true ? "" : 0, true ? "" : 0],
53094
+ onClick: handleClose,
53095
+ children: cancelText
53096
+ }), submitText && onSubmit && (0,jsx_runtime_namespaceObject.jsx)(Button_Button, {
53097
+ variant: "tertiary",
53098
+ css: [Notification_styles_actionBtnStyles(textColor), styleOverrides?.actionButton, true ? "" : 0, true ? "" : 0],
53099
+ onClick: handleSubmit,
53100
+ children: submitText
53101
+ })]
53102
+ })]
53103
+ })]
53104
+ });
53105
+ };
53106
+ ;// ./src/components/NotificationComponents/Notification/Notification.tsx
53107
+
53108
+
53109
+
53110
+
53111
+
53112
+
53113
+
53114
+
53115
+ /**
53116
+ * Notification — portal-based notification container driven by the Observer pattern.
53117
+ *
53118
+ * ## Usage pattern (two steps)
53119
+ *
53120
+ * **Step 1 — mount once** near the app root:
53121
+ * ```tsx
53122
+ * import { Notification, NotificationPositions } from '@ssa-ui-kit/core';
53123
+ *
53124
+ * <Notification position={NotificationPositions.rightBottom} withShadow />
53125
+ * ```
53126
+ *
53127
+ * **Step 2 — trigger from anywhere** via `showNotification`:
53128
+ * ```tsx
53129
+ * import { showNotification, NotificationVariants } from '@ssa-ui-kit/core';
53130
+ *
53131
+ * showNotification({
53132
+ * variant: NotificationVariants.neutral,
53133
+ * title: 'Jane Smith',
53134
+ * date: '5 minutes ago',
53135
+ * description: 'Left a comment on your post.',
53136
+ * icon: <img src={avatarUrl} alt="Jane" />,
53137
+ * });
53138
+ * ```
53139
+ *
53140
+ * ## Key props
53141
+ * - `position` — one of `NotificationPositions` (default: `rightBottom`)
53142
+ * - `timeout` — default auto-dismiss ms; omitted = persistent (default: `undefined`)
53143
+ * - `maxAmount` — cap on simultaneous notifications; oldest dropped when exceeded
53144
+ * - `withShadow` — drop shadow (default: `true`)
53145
+ * - `withBorder` — 1 px border (default: `false`)
53146
+ * - `styles` — `NotificationStyleOverrides` for per-slot CSS customization
53147
+ * - `containerSelector` — CSS selector for a custom portal target
53148
+ *
53149
+ * ## Stacking order
53150
+ * - `*-top` positions → newest prepended (top of stack)
53151
+ * - `*-bottom` positions → newest appended (bottom of stack)
53152
+ *
53153
+ * ## `timeout` precedence
53154
+ * Per-notification `timeout` from `showNotification` overrides the component-level default.
53155
+ * `undefined` at either level keeps the notification persistent.
53156
+ *
53157
+ * @category Components
53158
+ * @subcategory Notification
53159
+ */
53160
+
53161
+ let notificationIdCounter = 0;
53162
+ const generateNotificationId = () => `notification-${Date.now()}-${++notificationIdCounter}`;
53163
+ const Notification_Notification = props => {
53164
+ const {
53165
+ position = NotificationPositions.rightBottom,
53166
+ size = NotificationSizes.small,
53167
+ withShadow = true,
53168
+ withBorder = false,
53169
+ cancelText = '',
53170
+ submitText = '',
53171
+ containerSelector,
53172
+ animationDuration = 300,
53173
+ maxAmount,
53174
+ styles
53175
+ } = props;
53176
+
53177
+ // Notifications are persistent by default — `timeout` has no default value.
53178
+ // We use the `'timeout' in props` check so that passing `timeout={undefined}`
53179
+ // explicitly is respected as "disable auto-dismiss" and not silently ignored.
53180
+ const timeout = 'timeout' in props ? props.timeout : undefined;
53181
+ const [notifications, setNotifications] = (0,external_react_namespaceObject.useState)([]);
53182
+
53183
+ // Unique per-instance key so multiple <Notification> mounts coexist without
53184
+ // overwriting each other's observer subscription (e.g. Storybook Docs view).
53185
+ const instanceId = (0,external_react_namespaceObject.useId)();
53186
+
53187
+ // useEffectEvent gives a stable reference that always reads the latest props —
53188
+ // no manual refs needed. The effect subscribes once and never re-runs.
53189
+ const handleDispatch = (0,external_react_namespaceObject.useEffectEvent)(params => {
53190
+ // Per-notification `timeout` overrides the component-level default.
53191
+ const resolvedTimeout = 'timeout' in params ? params.timeout : timeout;
53192
+ const newNotification = {
53193
+ ...params,
53194
+ id: generateNotificationId(),
53195
+ resolvedTimeout
53196
+ };
53197
+ setNotifications(prev => {
53198
+ const isTop = position.includes('top');
53199
+ const next = isTop ? [newNotification, ...prev] : [...prev, newNotification];
53200
+ if (maxAmount && next.length > maxAmount) {
53201
+ return isTop ? next.slice(0, maxAmount) : next.slice(-maxAmount);
53202
+ }
53203
+ return next;
53204
+ });
53205
+ });
53206
+ (0,external_react_namespaceObject.useEffect)(() => {
53207
+ notificationObserver.subscribe(instanceId, handleDispatch);
53208
+ return () => notificationObserver.unsubscribe(instanceId);
53209
+ }, []);
53210
+ const removeNotification = id => {
53211
+ setNotifications(prev => prev.filter(n => n.id !== id));
53212
+ };
53213
+ if (notifications.length === 0 || typeof document === 'undefined') {
53214
+ return null;
53215
+ }
53216
+ const container = containerSelector && document.querySelector(containerSelector) || document.body;
53217
+ return /*#__PURE__*/(0,external_react_dom_namespaceObject.createPortal)((0,jsx_runtime_namespaceObject.jsx)("div", {
53218
+ css: containerStyles(position),
53219
+ children: notifications.map(notification => (0,jsx_runtime_namespaceObject.jsx)(NotificationItem, {
53220
+ ...notification,
53221
+ variant: notification.variant ?? NotificationVariants.secondary,
53222
+ size: notification.size ?? size,
53223
+ cancelText: notification.cancelText ?? cancelText,
53224
+ submitText: notification.submitText ?? submitText,
53225
+ withShadow: withShadow,
53226
+ withBorder: withBorder,
53227
+ animationDuration: animationDuration,
53228
+ position: position,
53229
+ timeout: notification.resolvedTimeout,
53230
+ onRemove: removeNotification,
53231
+ styleOverrides: styles
53232
+ }, notification.id))
53233
+ }), container);
53234
+ };
53235
+ /* harmony default export */ const NotificationComponents_Notification_Notification = (Notification_Notification);
53236
+ ;// ./src/components/NotificationComponents/index.ts
53237
+
53238
+
53239
+
53240
+
53241
+ // Aliased to avoid collisions with generic names from other components
51477
53242
  ;// ./src/components/AccordionGroup/index.ts
51478
53243
 
51479
53244
 
@@ -52378,6 +54143,13 @@ const UserProfile = ({
52378
54143
 
52379
54144
 
52380
54145
 
54146
+ // ============================================================================
54147
+ // Notification Components
54148
+ // ============================================================================
54149
+ // Alert, Toast, and Notification banners (Observer-driven, portal-based)
54150
+
54151
+
54152
+
52381
54153
  // ============================================================================
52382
54154
  // Specialized Components
52383
54155
  // ============================================================================