@flamingo-stack/openframe-frontend-core 0.0.383 → 0.0.384-snapshot.20260708165758

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 (120) hide show
  1. package/dist/{chunk-FL6YJLPC.cjs → chunk-2KDXNL7M.cjs} +7 -7
  2. package/dist/{chunk-FL6YJLPC.cjs.map → chunk-2KDXNL7M.cjs.map} +1 -1
  3. package/dist/{chunk-EGFLSQEJ.js → chunk-53R2NLYW.js} +4 -4
  4. package/dist/{chunk-W456FUZG.cjs → chunk-5I4ERVP4.cjs} +57 -57
  5. package/dist/{chunk-W456FUZG.cjs.map → chunk-5I4ERVP4.cjs.map} +1 -1
  6. package/dist/{chunk-BALSUWWU.cjs → chunk-5O2QED4E.cjs} +37 -37
  7. package/dist/{chunk-BALSUWWU.cjs.map → chunk-5O2QED4E.cjs.map} +1 -1
  8. package/dist/{chunk-EBOK7SG7.js → chunk-7DVZECFW.js} +2 -2
  9. package/dist/{chunk-SGMZQTP6.js → chunk-ACKCFE7D.js} +2 -2
  10. package/dist/{chunk-MY7TV47R.js → chunk-ACYF5YVM.js} +2 -2
  11. package/dist/{chunk-7TU6MLIQ.cjs → chunk-CIBBHSCP.cjs} +12 -12
  12. package/dist/{chunk-7TU6MLIQ.cjs.map → chunk-CIBBHSCP.cjs.map} +1 -1
  13. package/dist/{chunk-ZFIDR55N.cjs → chunk-DN5AIYP4.cjs} +9 -9
  14. package/dist/{chunk-ZFIDR55N.cjs.map → chunk-DN5AIYP4.cjs.map} +1 -1
  15. package/dist/{chunk-AJJF43ZL.js → chunk-IDF4BZHL.js} +2 -2
  16. package/dist/{chunk-6SR7HXGW.js → chunk-K557GA5G.js} +4 -4
  17. package/dist/{chunk-LI7OAUSA.js → chunk-L5FDBXYI.js} +4 -4
  18. package/dist/{chunk-AHCBZ6TC.cjs → chunk-LDOEIJIA.cjs} +62 -62
  19. package/dist/{chunk-AHCBZ6TC.cjs.map → chunk-LDOEIJIA.cjs.map} +1 -1
  20. package/dist/{chunk-WVJFSCXR.cjs → chunk-LFAEFMYA.cjs} +26 -26
  21. package/dist/{chunk-WVJFSCXR.cjs.map → chunk-LFAEFMYA.cjs.map} +1 -1
  22. package/dist/{chunk-S2V4EPKL.cjs → chunk-LRLY7VFQ.cjs} +15 -15
  23. package/dist/{chunk-S2V4EPKL.cjs.map → chunk-LRLY7VFQ.cjs.map} +1 -1
  24. package/dist/{chunk-KBG3PLKS.js → chunk-NTHZI6VG.js} +2 -2
  25. package/dist/{chunk-KQQ6U4UW.js → chunk-PNJXI4NZ.js} +4 -4
  26. package/dist/{chunk-KGSOMSHT.cjs → chunk-QXKQ2WZF.cjs} +25 -25
  27. package/dist/{chunk-KGSOMSHT.cjs.map → chunk-QXKQ2WZF.cjs.map} +1 -1
  28. package/dist/{chunk-B45U4FY5.js → chunk-UPHZGPXZ.js} +2 -2
  29. package/dist/{chunk-VQCV3GGI.cjs → chunk-VZHEFPPH.cjs} +227 -163
  30. package/dist/chunk-VZHEFPPH.cjs.map +1 -0
  31. package/dist/{chunk-YW6K7IYF.cjs → chunk-X5DBARHE.cjs} +31 -31
  32. package/dist/{chunk-YW6K7IYF.cjs.map → chunk-X5DBARHE.cjs.map} +1 -1
  33. package/dist/{chunk-CNDLMBFN.js → chunk-XQ77CK5C.js} +390 -326
  34. package/dist/chunk-XQ77CK5C.js.map +1 -0
  35. package/dist/{chunk-ZD6GZX7E.js → chunk-YDYO7DRL.js} +2 -2
  36. package/dist/{chunk-KFTCNBSJ.cjs → chunk-ZRVWRNVJ.cjs} +5 -5
  37. package/dist/{chunk-KFTCNBSJ.cjs.map → chunk-ZRVWRNVJ.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/approval-batch-message.d.ts +20 -0
  41. package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
  42. package/dist/components/chat/index.cjs +4 -2
  43. package/dist/components/chat/index.cjs.map +1 -1
  44. package/dist/components/chat/index.js +3 -1
  45. package/dist/components/contact/index.cjs +3 -3
  46. package/dist/components/contact/index.js +2 -2
  47. package/dist/components/docs/index.cjs +5 -5
  48. package/dist/components/docs/index.js +4 -4
  49. package/dist/components/embeds/index.cjs +3 -3
  50. package/dist/components/embeds/index.js +2 -2
  51. package/dist/components/faq/index.cjs +3 -3
  52. package/dist/components/faq/index.js +2 -2
  53. package/dist/components/features/index.cjs +2 -2
  54. package/dist/components/features/index.js +1 -1
  55. package/dist/components/features/notifications/approval-request-notification-tile.d.ts.map +1 -1
  56. package/dist/components/features/notifications/notification-tile.d.ts +8 -2
  57. package/dist/components/features/notifications/notification-tile.d.ts.map +1 -1
  58. package/dist/components/features/notifications/types.d.ts +11 -1
  59. package/dist/components/features/notifications/types.d.ts.map +1 -1
  60. package/dist/components/help-center-pages/index.cjs +20 -20
  61. package/dist/components/help-center-pages/index.js +11 -11
  62. package/dist/components/index.cjs +122 -120
  63. package/dist/components/index.cjs.map +1 -1
  64. package/dist/components/index.js +11 -9
  65. package/dist/components/index.js.map +1 -1
  66. package/dist/components/navigation/app-layout.d.ts +9 -1
  67. package/dist/components/navigation/app-layout.d.ts.map +1 -1
  68. package/dist/components/navigation/index.cjs +2 -2
  69. package/dist/components/navigation/index.js +1 -1
  70. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  71. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  72. package/dist/components/onboarding-guides/index.cjs +5 -5
  73. package/dist/components/onboarding-guides/index.js +4 -4
  74. package/dist/components/related-content/index.cjs +3 -3
  75. package/dist/components/related-content/index.js +2 -2
  76. package/dist/components/tickets/index.cjs +5 -5
  77. package/dist/components/tickets/index.js +4 -4
  78. package/dist/components/ui/data-table/data-table-body.d.ts +6 -1
  79. package/dist/components/ui/data-table/data-table-body.d.ts.map +1 -1
  80. package/dist/components/ui/data-table/data-table-row.d.ts +7 -1
  81. package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
  82. package/dist/components/ui/data-table/index.d.ts +1 -1
  83. package/dist/components/ui/index.cjs +4 -2
  84. package/dist/components/ui/index.cjs.map +1 -1
  85. package/dist/components/ui/index.js +3 -1
  86. package/dist/index.cjs +4 -2
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.js +3 -1
  89. package/dist/tailwind.config.cjs +4 -1
  90. package/dist/tailwind.config.cjs.map +1 -1
  91. package/dist/tailwind.config.js +4 -1
  92. package/dist/tailwind.config.js.map +1 -1
  93. package/dist/types/navigation.d.ts +0 -1
  94. package/dist/types/navigation.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/src/components/chat/approval-batch-message.tsx +28 -15
  97. package/src/components/features/notifications/approval-request-notification-tile.tsx +70 -46
  98. package/src/components/features/notifications/notification-tile.tsx +125 -41
  99. package/src/components/features/notifications/types.ts +11 -1
  100. package/src/components/navigation/app-layout.tsx +17 -1
  101. package/src/components/navigation/navigation-sidebar-item.tsx +1 -12
  102. package/src/components/navigation/navigation-sidebar.tsx +6 -1
  103. package/src/components/ui/data-table/data-table-body.tsx +7 -0
  104. package/src/components/ui/data-table/data-table-row.tsx +12 -1
  105. package/src/stories/NotificationDrawer.stories.tsx +220 -31
  106. package/src/types/navigation.ts +0 -1
  107. package/tailwind.config.ts +4 -1
  108. package/dist/chunk-CNDLMBFN.js.map +0 -1
  109. package/dist/chunk-VQCV3GGI.cjs.map +0 -1
  110. /package/dist/{chunk-EGFLSQEJ.js.map → chunk-53R2NLYW.js.map} +0 -0
  111. /package/dist/{chunk-EBOK7SG7.js.map → chunk-7DVZECFW.js.map} +0 -0
  112. /package/dist/{chunk-SGMZQTP6.js.map → chunk-ACKCFE7D.js.map} +0 -0
  113. /package/dist/{chunk-MY7TV47R.js.map → chunk-ACYF5YVM.js.map} +0 -0
  114. /package/dist/{chunk-AJJF43ZL.js.map → chunk-IDF4BZHL.js.map} +0 -0
  115. /package/dist/{chunk-6SR7HXGW.js.map → chunk-K557GA5G.js.map} +0 -0
  116. /package/dist/{chunk-LI7OAUSA.js.map → chunk-L5FDBXYI.js.map} +0 -0
  117. /package/dist/{chunk-KBG3PLKS.js.map → chunk-NTHZI6VG.js.map} +0 -0
  118. /package/dist/{chunk-KQQ6U4UW.js.map → chunk-PNJXI4NZ.js.map} +0 -0
  119. /package/dist/{chunk-B45U4FY5.js.map → chunk-UPHZGPXZ.js.map} +0 -0
  120. /package/dist/{chunk-ZD6GZX7E.js.map → chunk-YDYO7DRL.js.map} +0 -0
@@ -3,11 +3,21 @@ import { APPROVAL_STATUS, type ApprovalBatchData, type ChatApprovalStatus } from
3
3
 
4
4
  export type NotificationVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
5
5
 
6
- export type NotificationSeverity = 'INFO' | 'WARNING' | 'DANGER'
6
+ export type NotificationSeverity = 'INFO' | 'SUCCESS' | 'WARNING' | 'DANGER'
7
7
 
8
8
  export interface Notification {
9
9
  id: string
10
10
  variant?: NotificationVariant
11
+ /** Kind label rendered uppercase in the tile header (e.g. "New Customer Created"). */
12
+ type?: string
13
+ /** Custom icon for the tile header slot; inherits the severity color via `currentColor`. */
14
+ icon?: ReactNode
15
+ /**
16
+ * Image URL for the tile header slot; takes precedence over `icon`. Proxy external
17
+ * URLs on the consumer side (ad-blockers routinely block direct third-party avatars);
18
+ * on load failure the tile falls back to `icon`/dot.
19
+ */
20
+ imageUrl?: string
11
21
  title: ReactNode
12
22
  description?: ReactNode
13
23
  createdAt: number
@@ -67,6 +67,14 @@ export interface AppLayoutProps {
67
67
  * the drawer is part of the layout chrome, not page content.
68
68
  */
69
69
  drawer?: React.ReactNode
70
+ /**
71
+ * Full-width banner rendered ABOVE both the sidebar and the header, spanning
72
+ * the entire viewport width and pinned to the top of the layout. Optional —
73
+ * when omitted the layout is unchanged. Used for global, cross-page callouts
74
+ * (e.g. an onboarding "complete your setup" bar). The sidebar + header + main
75
+ * area occupy the remaining height below it.
76
+ */
77
+ topBar?: React.ReactNode
70
78
  }
71
79
 
72
80
  export function AppLayout({
@@ -79,6 +87,7 @@ export function AppLayout({
79
87
  mobileBurgerMenuProps,
80
88
  disabled = false,
81
89
  drawer,
90
+ topBar,
82
91
  }: AppLayoutProps) {
83
92
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
84
93
  const [drawerContainer, setDrawerContainer] = useState<HTMLDivElement | null>(null)
@@ -114,7 +123,13 @@ export function AppLayout({
114
123
  return (
115
124
  <AppLayoutDrawerContainerContext.Provider value={drawerContainer}>
116
125
  <AppLayoutDrawerCoordinationContext.Provider value={drawerCoordination}>
117
- <div className={cn("flex h-screen bg-ods-bg", className)}>
126
+ <div className={cn("flex flex-col h-screen bg-ods-bg", className)}>
127
+ {/* Full-width top banner above sidebar + header (optional) */}
128
+ {topBar}
129
+ {/* Sidebar + header + main occupy the remaining height below the banner.
130
+ `relative` so the tablet sidebar (position:absolute) anchors to this
131
+ row — below the topBar — instead of the viewport. */}
132
+ <div className="flex flex-1 min-h-0 relative">
118
133
  <NavigationSidebar config={sidebarConfig} disabled={disabled} />
119
134
  {/* Mobile Burger Menu - opens below header */}
120
135
  <MobileBurgerMenu
@@ -158,6 +173,7 @@ export function AppLayout({
158
173
  {drawer}
159
174
  </div>
160
175
  </div>
176
+ </div>
161
177
  </div>
162
178
  </AppLayoutDrawerCoordinationContext.Provider>
163
179
  </AppLayoutDrawerContainerContext.Provider>
@@ -72,22 +72,11 @@ export function NavigationSidebarItemButton({
72
72
 
73
73
  {hasUnread && showLabel && (
74
74
  <span className="bg-ods-accent flex items-center justify-center flex-shrink-0 p-2 rounded-md size-6">
75
- <span className="text-xs font-medium text-ods-text-on-accent">
75
+ <span className="text-h5 text-ods-text-on-accent">
76
76
  {unreadCount > 99 ? '99+' : unreadCount}
77
77
  </span>
78
78
  </span>
79
79
  )}
80
-
81
- {item.badge && showLabel && (
82
- <span
83
- className={cn(
84
- "text-sm flex-shrink-0 transition-colors duration-300",
85
- isActive && !disabled ? "text-ods-accent" : "text-ods-text-secondary",
86
- )}
87
- >
88
- {item.badge}
89
- </span>
90
- )}
91
80
  </button>
92
81
  )
93
82
  }
@@ -136,7 +136,12 @@ export function NavigationSidebar({ config, disabled = false }: NavigationSideba
136
136
  className={cn(
137
137
  "flex-col hidden md:flex flex-shrink-0",
138
138
  "bg-ods-card border-r border-ods-border",
139
- isTablet ? "fixed top-0 left-0 h-screen z-[45]" : "relative h-full",
139
+ // Tablet: float the sidebar over content. Anchored to the layout row
140
+ // (`absolute` within AppLayout's `relative` row) — NOT the viewport —
141
+ // so an optional `topBar` above the row is not overlapped. With no
142
+ // topBar the row spans the full viewport, so this is visually
143
+ // identical to a viewport-fixed sidebar.
144
+ isTablet ? "absolute inset-y-0 left-0 z-[45]" : "relative h-full",
140
145
  transitionsEnabled && "transition-[width] duration-300",
141
146
  config.className,
142
147
  )}
@@ -29,6 +29,11 @@ export interface DataTableBodyProps<T = any> {
29
29
  rowClassName?: string | ((item: T, index: number) => string)
30
30
  /** Dense row height. */
31
31
  compact?: boolean
32
+ /**
33
+ * Treat the design row height as a minimum so multi-line cell content grows
34
+ * the row instead of clipping. Default keeps the fixed height.
35
+ */
36
+ autoHeight?: boolean
32
37
  /**
33
38
  * Click anywhere on a row (except elements with `data-no-row-click`). Prefer
34
39
  * `useCallback` to avoid breaking `React.memo` on rows.
@@ -66,6 +71,7 @@ export function DataTableBody<T = any>({
66
71
  className,
67
72
  rowClassName,
68
73
  compact,
74
+ autoHeight,
69
75
  onRowClick,
70
76
  rowHref,
71
77
  minRows,
@@ -112,6 +118,7 @@ export function DataTableBody<T = any>({
112
118
  onClick={onRowClick}
113
119
  href={href}
114
120
  compact={compact}
121
+ autoHeight={autoHeight}
115
122
  className={cls}
116
123
  subRow={renderSubRow?.(item)}
117
124
  />
@@ -13,6 +13,12 @@ export interface DataTableRowProps<T> {
13
13
  href?: string | null
14
14
  /** Dense row height. */
15
15
  compact?: boolean
16
+ /**
17
+ * Treat the design row height as a minimum: a cell rendering multi-line
18
+ * content (e.g. a line-clamped description) grows the row instead of being
19
+ * clipped. Default keeps the fixed height.
20
+ */
21
+ autoHeight?: boolean
16
22
  className?: string
17
23
  /** Expandable content rendered below the cells, inside the same card. */
18
24
  subRow?: ReactNode
@@ -60,6 +66,7 @@ function DataTableRowImpl<T>({
60
66
  onClick,
61
67
  href,
62
68
  compact,
69
+ autoHeight,
63
70
  className,
64
71
  subRow,
65
72
  }: DataTableRowProps<T>) {
@@ -100,7 +107,11 @@ function DataTableRowImpl<T>({
100
107
  <div
101
108
  className={cn(
102
109
  'flex items-center gap-[var(--spacing-system-mf)] px-[var(--spacing-system-mf)]',
103
- compact ? 'py-[var(--spacing-system-xsf)]' : `py-0 ${ROW_HEIGHT_DESKTOP}`,
110
+ compact
111
+ ? 'py-[var(--spacing-system-xsf)]'
112
+ : autoHeight
113
+ ? 'py-[var(--spacing-system-sf)] min-h-[68px] md:min-h-[80px]'
114
+ : `py-0 ${ROW_HEIGHT_DESKTOP}`,
104
115
  hasSubRow && 'border-b border-ods-border',
105
116
  )}
106
117
  >
@@ -1,6 +1,11 @@
1
1
  import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2
2
  import * as React from 'react';
3
3
  import { Button } from '../components/ui/button';
4
+ import { BracketCurlyIcon } from '../components/icons-v2-generated/coding/bracket-curly-icon';
5
+ import { MonitorIcon } from '../components/icons-v2-generated/devices/monitor-icon';
6
+ import { AlertTriangleIcon } from '../components/icons-v2-generated/interface/alert-triangle-icon';
7
+ import { BannedIcon } from '../components/icons-v2-generated/security/banned-icon';
8
+ import { UserPlusIcon } from '../components/icons-v2-generated/users/user-plus-icon';
4
9
  import {
5
10
  ADMIN_APPROVAL_REQUEST_CONTEXT_TYPE,
6
11
  ApprovalRequestNotificationTile,
@@ -44,47 +49,100 @@ const meta = {
44
49
  export default meta;
45
50
  type Story = StoryObj<typeof meta>;
46
51
 
52
+ /** Inline avatar stand-in so the image slot renders without network access. */
53
+ const CUSTOMER_AVATAR =
54
+ 'data:image/svg+xml;utf8,' +
55
+ encodeURIComponent(
56
+ '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><rect width="32" height="32" fill="#5EFC8D"/><circle cx="16" cy="12" r="6" fill="#212121"/><path d="M4 32c0-6.6 5.4-12 12-12s12 5.4 12 12" fill="#212121"/></svg>',
57
+ );
58
+
59
+ /** Seed an approval-request notification in a given resolution state. */
60
+ const approvalSeed = (
61
+ n: number,
62
+ resolution: string | null,
63
+ resolvedByName: string | null,
64
+ minutesAgo: number,
65
+ ): Notification => ({
66
+ id: `seed-approval-${n}`,
67
+ variant: 'warning',
68
+ title: 'Clear stuck print queue',
69
+ description: 'Mingo wants to restart the Spooler service on SRV-PRINT01.',
70
+ createdAt: Date.now() - 1_000 * 60 * minutesAgo,
71
+ read: false,
72
+ meta: {
73
+ contextType: ADMIN_APPROVAL_REQUEST_CONTEXT_TYPE,
74
+ approvalRequestId: `seed-req-${n}`,
75
+ approvalType: 'ADMIN',
76
+ resolution,
77
+ resolvedByName,
78
+ toolCalls: [
79
+ {
80
+ toolExecutionRequestId: `seed-ter-${n}`,
81
+ toolName: 'run_command',
82
+ toolTitle: 'Restart print spooler',
83
+ toolType: 'OPENFRAME_RMM',
84
+ toolExplanation: 'Restarts the Spooler service to clear a stuck print queue.',
85
+ requiresApproval: true,
86
+ toolCallArguments: { command: 'Restart-Service -Name Spooler -Force' },
87
+ },
88
+ ],
89
+ },
90
+ });
91
+
47
92
  const SEED: Notification[] = [
48
93
  {
49
94
  id: 'seed-1',
50
- variant: 'success',
51
- title: 'Device Deployment Complete',
52
- description: 'HP EliteBook deployed to Marketing Dept',
95
+ severity: 'INFO',
96
+ type: 'New Customer Created',
97
+ imageUrl: CUSTOMER_AVATAR,
98
+ title: 'Acme Corp',
99
+ description: 'Added by John Smith',
53
100
  createdAt: Date.now() - 1_000 * 30,
54
101
  read: false,
55
102
  },
103
+ approvalSeed(1, null, null, 2),
56
104
  {
57
105
  id: 'seed-2',
58
106
  variant: 'success',
59
- title: 'Device Deployment Complete',
60
- description: 'HP EliteBook deployed to Marketing Dept',
107
+ type: 'Device Deployment Complete',
108
+ icon: <MonitorIcon size={16} />,
109
+ title: 'HP EliteBook',
110
+ description: 'Deployed to Marketing Dept',
61
111
  createdAt: Date.now() - 1_000 * 60 * 5,
62
112
  read: false,
63
113
  },
64
114
  {
65
115
  id: 'seed-3',
66
- variant: 'error',
67
- title: 'Policy Violation Detected',
68
- description: 'USB storage device connected to ACME-WS01',
116
+ severity: 'DANGER',
117
+ type: 'Policy Violation Detected',
118
+ icon: <BannedIcon size={16} />,
119
+ title: 'ACME-WS01',
120
+ description: 'USB storage device connected',
69
121
  createdAt: Date.now() - 1_000 * 60 * 12,
70
122
  read: false,
71
123
  },
72
124
  {
73
125
  id: 'seed-4',
74
- variant: 'error',
75
- title: 'Disk Space Check Failed',
76
- description: 'C: drive at 95% capacity on SRV-DB01',
126
+ severity: 'WARNING',
127
+ type: 'Disk Space Check Failed',
128
+ icon: <AlertTriangleIcon size={16} />,
129
+ title: 'SRV-DB01',
130
+ description: 'C: drive at 95% capacity',
77
131
  createdAt: Date.now() - 1_000 * 60 * 35,
78
132
  read: false,
79
133
  },
134
+ approvalSeed(2, 'APPROVED', 'John Smith', 45),
80
135
  {
81
136
  id: 'seed-5',
82
137
  variant: 'success',
83
- title: 'Script Execution Complete',
84
- description: 'Windows Update script finished on 45 devices',
138
+ type: 'Script Execution Complete',
139
+ icon: <BracketCurlyIcon size={16} />,
140
+ title: 'Windows Update',
141
+ description: 'Script finished on 45 devices',
85
142
  createdAt: Date.now() - 1_000 * 60 * 60,
86
143
  read: false,
87
144
  },
145
+ approvalSeed(3, 'REJECTED', 'Jane Doe', 90),
88
146
  {
89
147
  id: 'seed-6',
90
148
  variant: 'warning',
@@ -93,6 +151,7 @@ const SEED: Notification[] = [
93
151
  createdAt: Date.now() - 1_000 * 60 * 60 * 2,
94
152
  read: false,
95
153
  },
154
+ approvalSeed(4, 'CANCELLED', null, 180),
96
155
  ];
97
156
 
98
157
  function AutoOpen() {
@@ -104,7 +163,9 @@ function AutoOpen() {
104
163
  }
105
164
 
106
165
  /**
107
- * Static preview of every tile variant — settled (non-live) state.
166
+ * Static preview of every tile variant — settled (non-live) state. The `type`
167
+ * header label picks up the variant color; tiles without an icon/image fall
168
+ * back to the severity dot in the header slot.
108
169
  */
109
170
  export const AllTileVariants: Story = {
110
171
  render: () => {
@@ -115,6 +176,7 @@ export const AllTileVariants: Story = {
115
176
  notification={{
116
177
  id: 't-default',
117
178
  variant: 'default',
179
+ type: 'System Notice',
118
180
  title: 'Heads up',
119
181
  description: 'Neutral notification, no specific status.',
120
182
  ...base,
@@ -125,8 +187,9 @@ export const AllTileVariants: Story = {
125
187
  notification={{
126
188
  id: 't-success',
127
189
  variant: 'success',
128
- title: 'Device Deployment Complete',
129
- description: 'HP EliteBook deployed to Marketing Dept',
190
+ type: 'Device Deployment Complete',
191
+ title: 'HP EliteBook',
192
+ description: 'Deployed to Marketing Dept',
130
193
  ...base,
131
194
  }}
132
195
  onComplete={() => {}}
@@ -135,8 +198,9 @@ export const AllTileVariants: Story = {
135
198
  notification={{
136
199
  id: 't-error',
137
200
  variant: 'error',
138
- title: 'Policy Violation Detected',
139
- description: 'USB storage device connected to ACME-WS01',
201
+ type: 'Policy Violation Detected',
202
+ title: 'ACME-WS01',
203
+ description: 'USB storage device connected',
140
204
  ...base,
141
205
  }}
142
206
  onComplete={() => {}}
@@ -145,6 +209,7 @@ export const AllTileVariants: Story = {
145
209
  notification={{
146
210
  id: 't-warning',
147
211
  variant: 'warning',
212
+ type: 'Approval Required',
148
213
  title: 'Tech Required',
149
214
  description: 'Approval is required to execute the command.',
150
215
  ...base,
@@ -155,12 +220,97 @@ export const AllTileVariants: Story = {
155
220
  notification={{
156
221
  id: 't-info',
157
222
  variant: 'info',
223
+ type: 'Agent Update',
158
224
  title: 'Update available',
159
225
  description: 'A new agent version is ready to install.',
160
226
  ...base,
161
227
  }}
162
228
  onComplete={() => {}}
163
229
  />
230
+ <NotificationTile
231
+ notification={{
232
+ id: 't-legacy',
233
+ variant: 'default',
234
+ title: 'Legacy notification',
235
+ description: 'No type, icon or image — header shows the dot fallback.',
236
+ ...base,
237
+ }}
238
+ onComplete={() => {}}
239
+ />
240
+ </div>
241
+ );
242
+ },
243
+ };
244
+
245
+ /**
246
+ * The new header fields: `severity` colors the type label and icon slot
247
+ * (INFO grey, WARNING amber, DANGER red) and overrides `variant`; `imageUrl`
248
+ * wins over `icon`, which wins over the dot fallback.
249
+ */
250
+ export const SeverityIconAndImage: Story = {
251
+ render: () => {
252
+ const base = { createdAt: Date.now() - 60_000, read: true } as const;
253
+ return (
254
+ <div className="flex max-w-md flex-col gap-2">
255
+ <NotificationTile
256
+ notification={{
257
+ id: 's-image',
258
+ severity: 'INFO',
259
+ type: 'New Customer Created',
260
+ imageUrl: CUSTOMER_AVATAR,
261
+ title: 'Acme Corp',
262
+ description: 'Added by John Smith',
263
+ ...base,
264
+ }}
265
+ onComplete={() => {}}
266
+ />
267
+ <NotificationTile
268
+ notification={{
269
+ id: 's-icon-info',
270
+ severity: 'INFO',
271
+ type: 'New User Invited',
272
+ icon: <UserPlusIcon size={16} />,
273
+ title: 'jane.doe@acme.com',
274
+ description: 'Invited by John Smith',
275
+ ...base,
276
+ }}
277
+ onComplete={() => {}}
278
+ />
279
+ <NotificationTile
280
+ notification={{
281
+ id: 's-icon-warning',
282
+ severity: 'WARNING',
283
+ type: 'Disk Space Warning',
284
+ icon: <AlertTriangleIcon size={16} />,
285
+ title: 'SRV-DB01',
286
+ description: 'C: drive at 85% capacity',
287
+ ...base,
288
+ }}
289
+ onComplete={() => {}}
290
+ />
291
+ <NotificationTile
292
+ notification={{
293
+ id: 's-icon-danger',
294
+ severity: 'DANGER',
295
+ type: 'Policy Violation Detected',
296
+ icon: <BannedIcon size={16} />,
297
+ title: 'ACME-WS01',
298
+ description: 'USB storage device connected',
299
+ ...base,
300
+ }}
301
+ onComplete={() => {}}
302
+ />
303
+ <NotificationTile
304
+ notification={{
305
+ id: 's-dot-danger',
306
+ severity: 'DANGER',
307
+ type: 'Agent Offline',
308
+ title: 'ACME-WS07',
309
+ description: 'No icon configured — severity dot fallback.',
310
+ ...base,
311
+ }}
312
+ onComplete={() => {}}
313
+ />
164
314
  </div>
165
315
  );
166
316
  },
@@ -179,9 +329,11 @@ export const LiveTile: Story = {
179
329
  key={tick}
180
330
  notification={{
181
331
  id: 'live-tile',
182
- variant: 'success',
183
- title: 'Device Deployment Complete',
184
- description: 'HP EliteBook deployed to Marketing Dept',
332
+ severity: 'INFO',
333
+ type: 'New Customer Created',
334
+ imageUrl: CUSTOMER_AVATAR,
335
+ title: 'Acme Corp',
336
+ description: 'Added by John Smith',
185
337
  createdAt: Date.now(),
186
338
  read: false,
187
339
  }}
@@ -212,7 +364,9 @@ export const DrawerEmpty: Story = {
212
364
 
213
365
  /**
214
366
  * Drawer pre-seeded to mirror the Figma reference, with the toggle row and the
215
- * history button wired.
367
+ * history button wired. Includes approval-request notifications in every
368
+ * resolution state: pending (buttons), approved, rejected and cancelled
369
+ * (status tag + resolver name in place of the buttons).
216
370
  */
217
371
  export const DrawerWithSeedData: Story = {
218
372
  render: function DrawerWithSeedDataRender() {
@@ -222,11 +376,13 @@ export const DrawerWithSeedData: Story = {
222
376
  onHistoryClick={() => alert('Navigate to /notifications')}
223
377
  historyHref="/notifications"
224
378
  onShowPopupsChange={(v) => console.log('showPopups →', v)}
379
+ renderTile={renderApprovalTile}
225
380
  >
226
381
  <AutoOpen />
227
382
  <NotificationDrawer />
228
383
  <p className="text-h6 text-ods-text-secondary">
229
- Six seeded notifications matching the Figma reference.
384
+ Seeded notifications matching the Figma reference, including approvals
385
+ in all four resolution states.
230
386
  </p>
231
387
  </NotificationsProvider>
232
388
  );
@@ -312,8 +468,13 @@ function PlaygroundControls() {
312
468
  useNotifications();
313
469
  const approvalSeq = React.useRef(0);
314
470
 
315
- const fire = (variant: NotificationVariant, title: string, description: string) => {
316
- addNotification({ variant, title, description });
471
+ const fire = (
472
+ variant: NotificationVariant,
473
+ title: string,
474
+ description: string,
475
+ extra?: Partial<Pick<Notification, 'type' | 'icon' | 'imageUrl' | 'severity'>>,
476
+ ) => {
477
+ addNotification({ variant, title, description, ...extra });
317
478
  };
318
479
 
319
480
  const fireWithDeepLink = () => {
@@ -417,7 +578,9 @@ function PlaygroundControls() {
417
578
  <Button
418
579
  variant="accent"
419
580
  onClick={() =>
420
- fire('success', 'Device Deployment Complete', 'HP EliteBook deployed to Marketing Dept')
581
+ fire('success', 'HP EliteBook', 'Deployed to Marketing Dept', {
582
+ type: 'Device Deployment Complete',
583
+ })
421
584
  }
422
585
  >
423
586
  Fire success
@@ -425,23 +588,49 @@ function PlaygroundControls() {
425
588
  <Button
426
589
  variant="destructive"
427
590
  onClick={() =>
428
- fire('error', 'Policy Violation Detected', 'USB storage device connected to ACME-WS01')
591
+ fire('error', 'ACME-WS01', 'USB storage device connected', {
592
+ type: 'Policy Violation Detected',
593
+ severity: 'DANGER',
594
+ icon: <BannedIcon size={16} />,
595
+ })
429
596
  }
430
597
  >
431
- Fire error
598
+ Fire danger + icon
432
599
  </Button>
433
600
  <Button
434
601
  variant="outline"
435
- onClick={() => fire('warning', 'Tech Required', 'Approval is required to execute the command.')}
602
+ onClick={() =>
603
+ fire('warning', 'Tech Required', 'Approval is required to execute the command.', {
604
+ type: 'Approval Required',
605
+ severity: 'WARNING',
606
+ icon: <AlertTriangleIcon size={16} />,
607
+ })
608
+ }
436
609
  >
437
- Fire warning
610
+ Fire warning + icon
438
611
  </Button>
439
612
  <Button
440
613
  variant="transparent"
441
- onClick={() => fire('info', 'Update available', 'A new agent version is ready to install.')}
614
+ onClick={() =>
615
+ fire('info', 'Update available', 'A new agent version is ready to install.', {
616
+ type: 'Agent Update',
617
+ })
618
+ }
442
619
  >
443
620
  Fire info
444
621
  </Button>
622
+ <Button
623
+ variant="outline"
624
+ onClick={() =>
625
+ fire('info', 'Acme Corp', 'Added by John Smith', {
626
+ type: 'New Customer Created',
627
+ severity: 'INFO',
628
+ imageUrl: CUSTOMER_AVATAR,
629
+ })
630
+ }
631
+ >
632
+ Fire with image
633
+ </Button>
445
634
  <Button
446
635
  variant="outline"
447
636
  onClick={() => fire('default', 'Heads up', 'Neutral notification without a specific status.')}
@@ -94,7 +94,6 @@ export interface NavigationSidebarItem {
94
94
  label: string
95
95
  icon: React.ReactNode
96
96
  path?: string
97
- badge?: string | React.ReactNode
98
97
  unreadCount?: number
99
98
  isActive?: boolean
100
99
  onClick?: () => void
@@ -199,7 +199,10 @@ const config: Config = {
199
199
  pink: "var(--ods-flamingo-pink-base)",
200
200
  cyan: "var(--ods-flamingo-cyan-base)",
201
201
  },
202
- "open-yellow": "var(--ods-open-yellow-base)",
202
+ "open-yellow": {
203
+ DEFAULT: "var(--ods-open-yellow-base)",
204
+ secondary: "var(--ods-open-yellow-secondary)",
205
+ },
203
206
 
204
207
  // Attention tokens (ods-colors.css). Components reference these BY
205
208
  // NAME (error-state.tsx: `border-ods-attention-red-error` etc.) —