@flamingo-stack/openframe-frontend-core 0.0.850 → 0.0.851

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 (114) hide show
  1. package/dist/chat-protocol/events.d.ts +4 -1
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +90 -40
  4. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  5. package/dist/chat-protocol/nats-decoder.js +5 -1
  6. package/dist/chat-protocol/policy-review.d.ts +18 -0
  7. package/dist/chat-protocol/policy-review.d.ts.map +1 -0
  8. package/dist/chat-protocol/policy-review.js +55 -0
  9. package/dist/{chunk-ZDSC3GFX.cjs → chunk-2NGW73T2.cjs} +3 -3
  10. package/dist/{chunk-2QZUTNL3.cjs → chunk-2WZHNMQY.cjs} +67 -67
  11. package/dist/{chunk-R5JPZ6TY.cjs → chunk-5SEJEBAB.cjs} +8 -8
  12. package/dist/{chunk-TX4NTH22.cjs → chunk-B6D4WWKD.cjs} +36 -36
  13. package/dist/{chunk-FXLROQUT.cjs → chunk-CDW6JTAQ.cjs} +442 -459
  14. package/dist/{chunk-5PIR77V5.cjs → chunk-DN227KZH.cjs} +4 -4
  15. package/dist/{chunk-QJER2ZNA.cjs → chunk-FGSN5UNY.cjs} +3 -3
  16. package/dist/{chunk-ZGEWT3SF.cjs → chunk-HXURNKXI.cjs} +3 -3
  17. package/dist/{chunk-FRG724DU.cjs → chunk-KRTTH3ET.cjs} +4 -4
  18. package/dist/{chunk-YJZ7R3W5.cjs → chunk-KXFIYO4O.cjs} +8 -8
  19. package/dist/{chunk-4YBS4HIO.cjs → chunk-LM7EL3UQ.cjs} +444 -27
  20. package/dist/{chunk-LGYPCUHO.cjs → chunk-NQEPLUUW.cjs} +21 -21
  21. package/dist/{chunk-OHHDX3Z4.cjs → chunk-ODMZSAIY.cjs} +36 -36
  22. package/dist/{chunk-VRWBJL7B.cjs → chunk-OUP7L3U5.cjs} +50 -50
  23. package/dist/{chunk-5CBI2ZYV.cjs → chunk-PJNEBSP5.cjs} +5 -5
  24. package/dist/{chunk-6AJYGXXS.cjs → chunk-PPCRLSVR.cjs} +11 -11
  25. package/dist/{chunk-SA2NNWIJ.cjs → chunk-QCSA7GIX.cjs} +17 -62
  26. package/dist/{chunk-JWIITCVB.cjs → chunk-SPKAXETT.cjs} +42 -42
  27. package/dist/{chunk-CAGNWDVJ.cjs → chunk-U4RTWH5D.cjs} +42 -42
  28. package/dist/{chunk-IHY7E4EX.cjs → chunk-UFGPLXH6.cjs} +4 -4
  29. package/dist/{chunk-7ICHJVAT.cjs → chunk-UQK6J3ZS.cjs} +13 -13
  30. package/dist/{chunk-S3QXLD4X.cjs → chunk-VCFQHPOO.cjs} +37 -3
  31. package/dist/{chunk-IPGGSFR7.cjs → chunk-VU6N3NZS.cjs} +6 -6
  32. package/dist/{chunk-GAYLAA3L.cjs → chunk-VXIZCUNZ.cjs} +61 -61
  33. package/dist/{chunk-3W4ASAXN.cjs → chunk-WYROOMUZ.cjs} +2 -2
  34. package/dist/{chunk-XKOL5CNU.cjs → chunk-ZEPEFKHA.cjs} +811 -932
  35. package/dist/{cloud-tenants-TVB3DT3J.cjs → cloud-tenants-XH6GV4EI.cjs} +12 -13
  36. package/dist/components/case-studies/index.cjs +24 -25
  37. package/dist/components/chat/approval-batch-message.d.ts +5 -0
  38. package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
  39. package/dist/components/chat/approval-batch-message.js +36 -10
  40. package/dist/components/chat/index.cjs +8 -9
  41. package/dist/components/chat/policy-review.d.ts +27 -0
  42. package/dist/components/chat/policy-review.d.ts.map +1 -0
  43. package/dist/components/chat/policy-review.js +67 -0
  44. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
  45. package/dist/components/chat/stream/message-mutations.js +4 -1
  46. package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
  47. package/dist/components/chat/tool-execution-display.js +16 -5
  48. package/dist/components/chat/types/message.types.d.ts +40 -2
  49. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  50. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  51. package/dist/components/chat/utils/extract-incomplete-message-state.js +2 -0
  52. package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
  53. package/dist/components/chat/utils/message-segment-accumulator.js +3 -0
  54. package/dist/components/chat/utils/process-historical-messages.d.ts +2 -2
  55. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  56. package/dist/components/chat/utils/process-historical-messages.js +7 -1
  57. package/dist/components/contact/index.cjs +13 -14
  58. package/dist/components/docs/index.cjs +16 -17
  59. package/dist/components/embeds/index.cjs +13 -14
  60. package/dist/components/faq/index.cjs +14 -15
  61. package/dist/components/features/approval-level.d.ts +6 -12
  62. package/dist/components/features/approval-level.d.ts.map +1 -1
  63. package/dist/components/features/approval-level.js +45 -55
  64. package/dist/components/features/index.cjs +16 -16
  65. package/dist/components/features/notifications/approval-request-notification-tile.d.ts.map +1 -1
  66. package/dist/components/features/notifications/approval-request-notification-tile.js +71 -48
  67. package/dist/components/features/notifications/types.d.ts +2 -0
  68. package/dist/components/features/notifications/types.d.ts.map +1 -1
  69. package/dist/components/features/notifications/types.js +3 -1
  70. package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
  71. package/dist/components/features/policy-configuration-panel.js +75 -64
  72. package/dist/components/help-center-pages/index.cjs +78 -79
  73. package/dist/components/index.cjs +57 -59
  74. package/dist/components/meeting-scheduler/index.cjs +61 -61
  75. package/dist/components/navigation/index.cjs +15 -16
  76. package/dist/components/onboarding-guides/index.cjs +14 -15
  77. package/dist/components/product-demo/index.cjs +23 -24
  78. package/dist/components/tickets/index.cjs +17 -18
  79. package/dist/components/ui/file-manager/index.cjs +8 -9
  80. package/dist/components/ui/index.cjs +16 -18
  81. package/dist/components/ui/radio-group.d.ts.map +1 -1
  82. package/dist/components/ui/radio-group.js +8 -2
  83. package/dist/components/ui/tab-selector.d.ts +7 -1
  84. package/dist/components/ui/tab-selector.d.ts.map +1 -1
  85. package/dist/components/ui/tab-selector.js +22 -3
  86. package/dist/{devices-NGCTSXIU.cjs → devices-7PT6MPJI.cjs} +13 -14
  87. package/dist/{embeddable-chat-322NDFP5.cjs → embeddable-chat-5NY76K7K.cjs} +84 -85
  88. package/dist/index.cjs +23 -25
  89. package/dist/{knowledge-75PO5DYS.cjs → knowledge-J6N7IEVI.cjs} +13 -14
  90. package/dist/{logs-PZESOAIG.cjs → logs-D6KQP2BX.cjs} +19 -20
  91. package/dist/{policies-6XNMBFWO.cjs → policies-J7MCOL3C.cjs} +16 -17
  92. package/dist/{remote-session-Q2FIBCGT.cjs → remote-session-PXHA5KFK.cjs} +8 -9
  93. package/dist/{scripts-B3JW6TPF.cjs → scripts-VURXIHVS.cjs} +19 -20
  94. package/dist/{software-update-NWP542J7.cjs → software-update-6SHUBDIE.cjs} +16 -17
  95. package/dist/{tickets-board-7FLGQEHF.cjs → tickets-board-NLCN7WNX.cjs} +9 -9
  96. package/package.json +1 -1
  97. package/src/chat-protocol/events.ts +4 -1
  98. package/src/chat-protocol/nats-decoder.ts +4 -0
  99. package/src/chat-protocol/policy-review.ts +71 -0
  100. package/src/components/chat/approval-batch-message.tsx +47 -10
  101. package/src/components/chat/policy-review.tsx +90 -0
  102. package/src/components/chat/stream/message-mutations.ts +4 -0
  103. package/src/components/chat/tool-execution-display.tsx +23 -4
  104. package/src/components/chat/types/message.types.ts +42 -2
  105. package/src/components/chat/utils/extract-incomplete-message-state.ts +2 -0
  106. package/src/components/chat/utils/message-segment-accumulator.ts +3 -0
  107. package/src/components/chat/utils/process-historical-messages.ts +14 -3
  108. package/src/components/features/approval-level.tsx +74 -78
  109. package/src/components/features/notifications/approval-request-notification-tile.tsx +75 -50
  110. package/src/components/features/notifications/types.ts +4 -0
  111. package/src/components/features/policy-configuration-panel.tsx +100 -83
  112. package/src/components/ui/radio-group.tsx +20 -2
  113. package/src/components/ui/tab-selector.tsx +26 -3
  114. package/dist/chunk-N3MBQRCQ.cjs +0 -41
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { ChevronDown, Shield } from 'lucide-react';
4
3
  import type { FC } from 'react';
5
4
  import { useState } from 'react';
6
5
  import type {
@@ -11,17 +10,21 @@ import type {
11
10
  } from '../../types/permissions';
12
11
  import { cn } from '../../utils/cn';
13
12
  import { useCollapsible } from '../chat/hooks/use-collapsible';
13
+ import { Chevron02DownIcon, ShieldIcon } from '../icons-v2-generated';
14
14
  import { TextLineSkeleton } from '../loading/unified-skeleton';
15
15
  import { ToolIcon } from '../tool-icon';
16
16
  import { Button } from '../ui/button';
17
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/dropdown-menu';
17
+ import {
18
+ DropdownMenu,
19
+ DropdownMenuContent,
20
+ DropdownMenuItem,
21
+ DropdownMenuSeparator,
22
+ DropdownMenuTrigger,
23
+ } from '../ui/dropdown-menu';
18
24
  import { loadingItemProps, useLoadingScope } from '../ui/loading-scope';
19
- import { APPROVAL_LEVEL_META, APPROVAL_LEVELS } from './approval-level';
25
+ import { APPROVAL_LEVEL_META, APPROVAL_LEVELS, ApprovalLevelView } from './approval-level';
20
26
 
21
- const approvalLevelOptions = APPROVAL_LEVELS.map(value => ({ value, label: APPROVAL_LEVEL_META[value].label }));
22
-
23
- const POLICY_LEVEL_WIDTH = 'w-[180px]';
24
- const CATEGORY_LEVEL_WIDTH = 'w-[256px]';
27
+ const CATEGORY_LEVEL_WIDTH = 'w-[296px]';
25
28
 
26
29
  /** How many collapsed category rows the panel draws while loading when the host passes no categories. */
27
30
  export const POLICY_CONFIGURATION_PANEL_SKELETON_CATEGORIES = 4;
@@ -40,9 +43,22 @@ const CATEGORY_LEVEL_COLUMN_CLASS = cn(
40
43
  CATEGORY_LEVEL_WIDTH,
41
44
  );
42
45
  /** The expand / collapse control's chevron. */
43
- const CHEVRON_CLASS = 'h-6 w-6 text-ods-text-primary transition-transform duration-300';
46
+ const CHEVRON_CLASS = '!h-6 !w-6 text-ods-text-primary transition-transform duration-300';
47
+
48
+ /** A level as the dropdown writes it: its icon in its colour (the trigger's chevron size, 24px), then its name. */
49
+ const LevelOption: FC<{ level: ApprovalLevel }> = ({ level }) => {
50
+ const { label, Icon, toneClassName } = APPROVAL_LEVEL_META[level];
51
+ return (
52
+ <span className="inline-flex min-w-0 items-center gap-[var(--spacing-system-xsf)]">
53
+ <Icon size={24} className={cn('shrink-0', toneClassName)} aria-hidden />
54
+ <span className="truncate">{label}</span>
55
+ </span>
56
+ );
57
+ };
44
58
 
45
- const getApprovalLevelLabel = (level: ApprovalLevel): string => APPROVAL_LEVEL_META[level]?.label ?? level;
59
+ /** A dropdown item at its trigger's scale: the trigger's type and icon size, a field's height (48px). The menu is
60
+ * portalled to the window, so it steps with the window (`md:`), not the content. */
61
+ const LEVEL_MENU_ITEM_CLASS = 'p-[var(--spacing-system-sf)] text-h4 [&_svg]:size-4 md:[&_svg]:size-6';
46
62
 
47
63
  const ApprovalLevelDropdown: FC<{
48
64
  value: ApprovalLevel | undefined;
@@ -58,27 +74,31 @@ const ApprovalLevelDropdown: FC<{
58
74
  <Button
59
75
  variant="outline"
60
76
  disabled={disabled}
61
- rightIcon={<ChevronDown className="h-6 w-6 text-ods-text-secondary" />}
77
+ rightIcon={<Chevron02DownIcon className="!h-6 !w-6 text-ods-text-secondary" />}
62
78
  className={cn(
63
- 'flex h-auto items-center justify-between gap-[var(--spacing-system-xsf)] rounded-md border border-ods-border bg-ods-card !px-[var(--spacing-system-xsf)] py-[var(--spacing-system-xsf)] transition-colors !text-h6 hover:bg-ods-bg-hover',
79
+ 'flex h-auto items-center justify-between gap-[var(--spacing-system-xsf)] rounded-md border border-ods-border bg-ods-card !p-[var(--spacing-system-sf)] transition-colors !text-h4 hover:bg-ods-bg-hover',
64
80
  value ? 'text-ods-text-primary' : 'text-ods-text-secondary',
65
81
  triggerClassName,
66
82
  )}
67
83
  >
68
- {value ? getApprovalLevelLabel(value) : 'Set Global Permission'}
84
+ {value ? <LevelOption level={value} /> : 'Set Global Permission'}
69
85
  </Button>
70
86
  </DropdownMenuTrigger>
71
87
  <DropdownMenuContent align="end" className={contentClassName}>
72
- {allowClear && (
73
- <DropdownMenuItem onClick={() => onChange(undefined)} className="text-h6">
74
- Clear Global Permission
75
- </DropdownMenuItem>
76
- )}
77
- {approvalLevelOptions.map(option => (
78
- <DropdownMenuItem key={option.value} onClick={() => onChange(option.value)} className="text-h6">
79
- {option.label}
88
+ {APPROVAL_LEVELS.map(level => (
89
+ <DropdownMenuItem key={level} onClick={() => onChange(level)} className={LEVEL_MENU_ITEM_CLASS}>
90
+ <LevelOption level={level} />
80
91
  </DropdownMenuItem>
81
92
  ))}
93
+ {/* The way back out comes last, apart from the levels. */}
94
+ {allowClear && (
95
+ <>
96
+ <DropdownMenuSeparator />
97
+ <DropdownMenuItem onClick={() => onChange(undefined)} className={LEVEL_MENU_ITEM_CLASS}>
98
+ Clear Global Permission
99
+ </DropdownMenuItem>
100
+ </>
101
+ )}
82
102
  </DropdownMenuContent>
83
103
  </DropdownMenu>
84
104
  );
@@ -89,56 +109,49 @@ const PolicyRow: FC<{
89
109
  editMode: boolean;
90
110
  onPermissionChange: (categoryId: string, policyId: string, level: ApprovalLevel) => void;
91
111
  }> = ({ policy, categoryId, editMode, onPermissionChange }) => {
92
- const handleChange = (level: ApprovalLevel | undefined) => {
93
- if (level) onPermissionChange(categoryId, policy.id, level);
94
- };
112
+ const nameId = `policy-${categoryId}-${policy.id}`;
113
+ // The level as every guardrail surface draws it: its icon in its colour, then its name (Figma 1608-49525), or, while
114
+ // editing, its name in its colour beside the four levels' icons to pick from (Figma 4365-22663).
115
+ const level = policy.approvalLevel ? (
116
+ <ApprovalLevelView
117
+ value={policy.approvalLevel}
118
+ editable={editMode}
119
+ onChange={next => onPermissionChange(categoryId, policy.id, next)}
120
+ aria-labelledby={nameId}
121
+ />
122
+ ) : null;
95
123
 
96
124
  return (
97
125
  <div className="flex items-start gap-[var(--spacing-system-m)] border-b border-ods-border bg-ods-bg px-[var(--spacing-system-m)] py-[var(--spacing-system-sf)] last:border-b-0 content-md:items-center">
98
- {/* Tool Icon */}
99
- <div className={ICON_TILE_CLASS}>
126
+ {/* Tool Icon: grey, as the category's (Figma 1608-49526). Editing on a phone it moves beside the name, and the
127
+ strip gets the row's width (Figma 4365-22455). */}
128
+ <div className={cn(ICON_TILE_CLASS, 'text-ods-text-secondary', editMode && 'hidden content-md:flex')}>
100
129
  <ToolIcon toolType={policy.toolName} size={16} />
101
130
  </div>
102
131
 
103
- {/* Policy Info — on mobile the approval level stacks below the pattern */}
132
+ {/* Policy Info — on mobile the approval level stacks below the name */}
104
133
  <div className="flex min-w-0 flex-1 flex-col">
105
- <p className="truncate text-ods-text-primary text-h4" title={policy.name}>
106
- {policy.name}
107
- </p>
108
- <p className="break-all text-ods-text-secondary text-h6">{policy.commandPattern}</p>
109
- {editMode ? (
110
- <div className="mt-[var(--spacing-system-xsf)] content-md:hidden">
111
- <ApprovalLevelDropdown
112
- value={policy.approvalLevel}
113
- onChange={handleChange}
114
- triggerClassName="w-full"
115
- contentClassName={POLICY_LEVEL_WIDTH}
116
- />
117
- </div>
118
- ) : policy.approvalLevel ? (
119
- <span className="text-ods-text-primary text-h4 content-md:hidden">
120
- {getApprovalLevelLabel(policy.approvalLevel)}
121
- </span>
122
- ) : null}
123
- </div>
124
-
125
- {/* Approval Level column (desktop only) */}
126
- <div className="hidden shrink-0 content-md:block">
127
- {editMode ? (
128
- <ApprovalLevelDropdown
129
- value={policy.approvalLevel}
130
- onChange={handleChange}
131
- triggerClassName={POLICY_LEVEL_WIDTH}
132
- contentClassName={POLICY_LEVEL_WIDTH}
133
- />
134
- ) : (
135
- <div className={cn('px-[var(--spacing-system-sf)] py-[var(--spacing-system-xsf)]', POLICY_LEVEL_WIDTH)}>
136
- {policy.approvalLevel && (
137
- <span className="text-ods-text-primary text-h4">{getApprovalLevelLabel(policy.approvalLevel)}</span>
138
- )}
134
+ <div className="flex min-w-0 items-center gap-[var(--spacing-system-sf)]">
135
+ <div className="flex min-w-0 flex-1 flex-col">
136
+ {/* The rule's name only: its command pattern is a regex, not something to read. */}
137
+ <p id={nameId} className="truncate text-ods-text-primary text-h4" title={policy.name}>
138
+ {policy.name}
139
+ </p>
139
140
  </div>
141
+ {editMode && (
142
+ <span className="flex shrink-0 text-ods-text-secondary content-md:hidden">
143
+ <ToolIcon toolType={policy.toolName} size={16} />
144
+ </span>
145
+ )}
146
+ </div>
147
+ {level && (
148
+ <div className={cn('flex content-md:hidden', editMode && 'mt-[var(--spacing-system-sf)]')}>{level}</div>
140
149
  )}
141
150
  </div>
151
+
152
+ {/* Approval Level column (desktop only). A flex box, not a block: a block's line box around the inline level
153
+ stood taller than the level and set it above the row's centre. */}
154
+ {level && <div className="hidden shrink-0 items-center content-md:flex">{level}</div>}
142
155
  </div>
143
156
  );
144
157
  };
@@ -186,15 +199,21 @@ const CategorySection: FC<{
186
199
  {/* Category Header */}
187
200
  <div
188
201
  {...loadingItemProps(props)}
189
- className={cn(CATEGORY_HEADER_CLASS, !loading && 'cursor-pointer transition-colors hover:bg-ods-bg-hover')}
202
+ // The whole row toggles, so the whole row lights on hover; not while the pointer is on the bulk dropdown, which
203
+ // is its own target. Open, a rule under the row (on the frame below) ends the lit row at an edge.
204
+ className={cn(
205
+ CATEGORY_HEADER_CLASS,
206
+ !loading &&
207
+ 'cursor-pointer transition-colors hover:bg-ods-bg-hover [&:has([data-bulk-level]:hover)]:bg-ods-card',
208
+ )}
190
209
  onClick={loading ? undefined : () => onToggle(category.id)}
191
210
  >
192
211
  {/* Category Icon (static: the host's, else the default shield) */}
193
212
  <div className={ICON_TILE_CLASS}>
194
- <div className="text-ods-text-secondary">{category.icon ?? <Shield className="h-4 w-4" />}</div>
213
+ <div className="text-ods-text-secondary">{category.icon ?? <ShieldIcon size={16} />}</div>
195
214
  </div>
196
215
 
197
- {/* Category Info — on mobile the bulk dropdown stacks below the count */}
216
+ {/* Category Info. A phone has no bulk dropdown (Figma 4365-22446): its rules are set one by one. */}
198
217
  <div className={CATEGORY_TEXT_CLASS}>
199
218
  {category.name ? (
200
219
  <p className="truncate text-ods-text-primary text-h4" title={category.name}>
@@ -206,25 +225,15 @@ const CategorySection: FC<{
206
225
  {loading ? (
207
226
  <TextLineSkeleton type="h6" className="w-24" />
208
227
  ) : (
209
- <p className="text-ods-text-secondary text-h6">{category.policies.length} Configurations</p>
210
- )}
211
- {editMode && (
212
- <div className="mt-[var(--spacing-system-xsf)] content-md:hidden" onClick={e => e.stopPropagation()}>
213
- <ApprovalLevelDropdown
214
- value={bulkLevel}
215
- onChange={handleBulkChange}
216
- allowClear
217
- triggerClassName="w-full"
218
- contentClassName={CATEGORY_LEVEL_WIDTH}
219
- disabled={loading}
220
- />
221
- </div>
228
+ <p className="text-ods-text-secondary text-h6">
229
+ {category.policies.length} {category.policies.length === 1 ? 'Configuration' : 'Configurations'}
230
+ </p>
222
231
  )}
223
232
  </div>
224
233
 
225
234
  {/* Global Permission column (desktop only) */}
226
235
  {editMode ? (
227
- <div className="hidden shrink-0 content-md:block" onClick={e => e.stopPropagation()}>
236
+ <div data-bulk-level className="hidden shrink-0 content-md:block" onClick={e => e.stopPropagation()}>
228
237
  <ApprovalLevelDropdown
229
238
  value={bulkLevel}
230
239
  onChange={handleBulkChange}
@@ -247,8 +256,13 @@ const CategorySection: FC<{
247
256
  e.stopPropagation();
248
257
  onToggle(category.id);
249
258
  }}
250
- leftIcon={<ChevronDown className={cn(CHEVRON_CLASS, isExpanded && 'rotate-180')} />}
251
- className="h-auto w-auto shrink-0 rounded-md"
259
+ leftIcon={<Chevron02DownIcon className={cn(CHEVRON_CLASS, isExpanded && 'rotate-180')} />}
260
+ // 40px on a phone (Figma 4365-22452), 48 from content-md.
261
+ className={cn(
262
+ 'h-auto w-auto shrink-0 rounded-md !p-[var(--spacing-system-xsf)] content-md:!p-[var(--spacing-system-sf)]',
263
+ // The row is the hover; the chevron adds none of its own.
264
+ 'hover:bg-transparent active:bg-transparent',
265
+ )}
252
266
  aria-label={isExpanded ? 'Collapse' : 'Expand'}
253
267
  />
254
268
  </div>
@@ -264,7 +278,10 @@ const CategorySection: FC<{
264
278
  }}
265
279
  className={isExpanded ? 'opacity-100' : 'opacity-0'}
266
280
  >
267
- <div ref={innerRef} className="px-[var(--spacing-system-mf)] pb-[var(--spacing-system-mf)] pt-0">
281
+ <div
282
+ ref={innerRef}
283
+ className="border-t border-ods-border px-[var(--spacing-system-mf)] pb-[var(--spacing-system-mf)] pt-[var(--spacing-system-mf)]"
284
+ >
268
285
  <div className="overflow-hidden rounded-md border border-ods-border">
269
286
  {/* No rows while the policies are read: nothing read is drawn, even collapsed. */}
270
287
  {(loading ? [] : category.policies).map(policy => (
@@ -290,9 +307,9 @@ const CategorySection: FC<{
290
307
  * background data refreshes never collapse the user's view. Bulk selections
291
308
  * reset when `editMode` changes.
292
309
  *
293
- * Responsive: below `md` the approval level / dropdowns stack inside the text
294
- * column (per the ODS mobile design); at `md+` they render as fixed-width
295
- * right-hand columns.
310
+ * Responsive (content width): below `content-md` a rule's level stacks under its
311
+ * pattern, and a category has no bulk dropdown (Figma 4365-22405); from
312
+ * `content-md` both are right-hand columns.
296
313
  */
297
314
  /** One shared empty map, so a reset that changes nothing changes no identity. */
298
315
  const NO_BULK_LEVELS: Record<string, ApprovalLevel | undefined> = {};
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
4
- import { type ComponentPropsWithoutRef, type ComponentRef, type ReactNode, forwardRef } from 'react';
4
+ import { type ComponentPropsWithoutRef, type ComponentRef, type ReactNode, forwardRef, useState } from 'react';
5
5
 
6
6
  import { CONTROL_SIZE } from '../../styles/component-sizes';
7
7
  import { cn } from '../../utils/cn';
@@ -126,6 +126,10 @@ const RadioGroupBlock = forwardRef<ComponentRef<typeof RadioGroupPrimitive.Root>
126
126
  loading && options.length === 0 ? Array.from({ length: skeletonCount }, () => null) : options;
127
127
  const isGrouped = variant === 'grouped';
128
128
  const isHorizontal = orientation === 'horizontal';
129
+ // The options the block first showed loaded. One added after that (a row the user's action brought in) fades in, as
130
+ // new rows do across the system; the first options, and the skeleton's swap to them, do not.
131
+ const [firstOptions, setFirstOptions] = useState<ReadonlySet<string> | null>(null);
132
+ if (!loading && firstOptions === null) setFirstOptions(new Set(options.map(option => option.value)));
129
133
  return (
130
134
  <div className={cn('relative flex w-full flex-col', className)}>
131
135
  <RadioGroupPrimitive.Root
@@ -215,7 +219,13 @@ const RadioGroupBlock = forwardRef<ComponentRef<typeof RadioGroupPrimitive.Root>
215
219
  )
216
220
  )}
217
221
  </div>
218
- {option?.trailing && <div className="ml-auto flex shrink-0 items-center">{option.trailing}</div>}
222
+ {/* The row's full height, its content centred whatever the row aligns (a design may top-align the dot with
223
+ the label); a trailing node can still place itself (`self-start`). */}
224
+ {option?.trailing && (
225
+ <div data-radio-trailing className="ml-auto flex shrink-0 items-center self-stretch">
226
+ {option.trailing}
227
+ </div>
228
+ )}
219
229
  </>
220
230
  );
221
231
  return (
@@ -236,6 +246,10 @@ const RadioGroupBlock = forwardRef<ComponentRef<typeof RadioGroupPrimitive.Root>
236
246
  // CheckboxBlock — the border stays put so an unselected row
237
247
  // never flashes "selected" under the cursor.
238
248
  interactive && 'hover:bg-ods-bg-hover active:bg-ods-bg-active',
249
+ // A control in `trailing` (a button, a menu) is its own target: on it, the row is not hovered or
250
+ // pressed, so the row never looks like what the click will do.
251
+ interactive &&
252
+ '[&:has([data-radio-trailing]_:active)]:bg-ods-card [&:has([data-radio-trailing]_:hover)]:bg-ods-card',
239
253
  isGrouped
240
254
  ? cn(
241
255
  'bg-ods-card px-[var(--spacing-system-sf)] py-[var(--spacing-system-xs,8px)]',
@@ -254,6 +268,10 @@ const RadioGroupBlock = forwardRef<ComponentRef<typeof RadioGroupPrimitive.Root>
254
268
  // `Tag`'s label is a span too, and it went invisible against
255
269
  // its own fill.
256
270
  isDisabled && 'bg-ods-bg',
271
+ option &&
272
+ firstOptions !== null &&
273
+ !firstOptions.has(option.value) &&
274
+ 'duration-300 animate-in fade-in slide-in-from-top-2 motion-reduce:animate-none',
257
275
  // With a footer the option stacks: its row, then the footer.
258
276
  hasFooter && 'flex-col items-stretch gap-[var(--spacing-system-sf)]',
259
277
  itemClassName,
@@ -30,6 +30,12 @@ export interface TabSelectorProps {
30
30
  items: TabSelectorItem[];
31
31
  /** Visual variant: primary (accent bg) or secondary (soft grey bg) */
32
32
  variant?: 'primary' | 'secondary';
33
+ /**
34
+ * `compact`: an icon-only strip at its content's width: 44px tall, 36px segments and 20px icons from `content-md`,
35
+ * 40 / 32 / 16px below it (the guardrails level picker, Figma 4365-22670 / 4365-22462). Default: the full-width
36
+ * strip, 48px at `content-md`.
37
+ */
38
+ size?: 'default' | 'compact';
33
39
  /** Optional label displayed above the selector */
34
40
  label?: string;
35
41
  /** Disable the entire selector (overrides per-item enabled state) */
@@ -51,6 +57,7 @@ export function TabSelector({
51
57
  onValueChange,
52
58
  items,
53
59
  variant = 'primary',
60
+ size = 'default',
54
61
  label,
55
62
  disabled,
56
63
  scrollable = false,
@@ -63,6 +70,7 @@ export function TabSelector({
63
70
  // renders in its title bar adapts by context, with no re-styling at the
64
71
  // call site.
65
72
  const flush = useIsInActionsMenuHeader();
73
+ const compact = size === 'compact';
66
74
  return (
67
75
  <div
68
76
  className={cn('flex flex-col gap-[var(--spacing-system-xxs)]', disabled && 'opacity-50', className)}
@@ -79,7 +87,11 @@ export function TabSelector({
79
87
  'flex gap-[var(--spacing-system-xxs)] bg-ods-bg p-[var(--spacing-system-xxs)]',
80
88
  flush
81
89
  ? 'h-10 w-full'
82
- : cn('h-11 rounded-md border border-ods-border content-md:h-12', scrollable ? 'overflow-x-auto' : 'w-full'),
90
+ : cn(
91
+ 'rounded-md border border-ods-border',
92
+ compact ? 'h-10 content-md:h-11' : 'h-11 content-md:h-12',
93
+ scrollable ? 'overflow-x-auto' : compact ? 'w-auto' : 'w-full',
94
+ ),
83
95
  )}
84
96
  >
85
97
  {items.map(item => {
@@ -101,10 +113,21 @@ export function TabSelector({
101
113
  data-tone={variant === 'primary' ? 'accent' : undefined}
102
114
  // scrollable: intrinsic widths must sum past the container or nothing ever overflows (flex-1's basis-0
103
115
  // defeats the scroll).
104
- className={cn('h-full', scrollable ? 'flex-none shrink-0 basis-auto' : 'flex-1')}
116
+ className={cn(
117
+ 'h-full',
118
+ compact ? 'w-8 flex-none content-md:w-9' : scrollable ? 'flex-none shrink-0 basis-auto' : 'flex-1',
119
+ )}
105
120
  >
106
121
  {item.icon && (
107
- <span className="flex size-4 shrink-0 items-center justify-center content-md:size-6">{item.icon}</span>
122
+ <span
123
+ className={cn(
124
+ 'flex shrink-0 items-center justify-center',
125
+ // compact: the box sizes the icon, whatever size the caller drew it at.
126
+ compact ? 'size-4 content-md:size-5 [&>svg]:size-full' : 'size-4 content-md:size-6',
127
+ )}
128
+ >
129
+ {item.icon}
130
+ </span>
108
131
  )}
109
132
  {item.label}
110
133
  {item.badge && item.badge}
@@ -1,41 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }"use client";
2
-
3
-
4
- var _chunk63A4GZMBcjs = require('./chunk-63A4GZMB.cjs');
5
-
6
- // src/components/ui/label.tsx
7
- var _reactlabel = require('@radix-ui/react-label'); var LabelPrimitive = _interopRequireWildcard(_reactlabel);
8
- var _classvarianceauthority = require('class-variance-authority');
9
- var _react = require('react');
10
- var _jsxruntime = require('react/jsx-runtime');
11
- var LABEL_TEXT_CLASS = "block text-ods-text-primary text-h6";
12
- var labelVariants = _classvarianceauthority.cva.call(void 0, "", {
13
- variants: {
14
- variant: {
15
- default: LABEL_TEXT_CLASS,
16
- /** @deprecated alias of `default` — the three small scales converged on text-h6. */
17
- small: LABEL_TEXT_CLASS,
18
- /** @deprecated alias of `default`. */
19
- medium: LABEL_TEXT_CLASS,
20
- large: _chunk63A4GZMBcjs.cn.call(void 0, LABEL_TEXT_CLASS, "text-h4"),
21
- /** A click surface that labels its control: no typography, the site's `className` is its look. */
22
- surface: ""
23
- },
24
- spacing: {
25
- none: "",
26
- tight: "mb-0.5",
27
- normal: "mb-2",
28
- loose: "mb-3"
29
- }
30
- },
31
- defaultVariants: {
32
- variant: "default",
33
- spacing: "none"
34
- }
35
- });
36
- var Label = _react.forwardRef.call(void 0, ({ className, variant, spacing, ...props }, ref) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, LabelPrimitive.Root, { ref, className: _chunk63A4GZMBcjs.cn.call(void 0, labelVariants({ variant, spacing }), className), ...props }));
37
- Label.displayName = LabelPrimitive.Root.displayName;
38
-
39
-
40
-
41
- exports.Label = Label;