@flamingo-stack/openframe-frontend-core 0.0.441 → 0.0.442-snapshot.20260717134227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-J5NO7PAG.cjs → chunk-2CJ734N2.cjs} +3 -3
- package/dist/{chunk-J5NO7PAG.cjs.map → chunk-2CJ734N2.cjs.map} +1 -1
- package/dist/{chunk-UFBUQO7Z.cjs → chunk-323TCNF3.cjs} +18 -18
- package/dist/{chunk-UFBUQO7Z.cjs.map → chunk-323TCNF3.cjs.map} +1 -1
- package/dist/{chunk-63TSTDME.js → chunk-3ZE55IH6.js} +5 -5
- package/dist/{chunk-Y3NFB6HR.js → chunk-5U6OZ5FD.js} +4 -4
- package/dist/{chunk-IAZ5AN2E.js → chunk-6IPNXMB7.js} +6 -6
- package/dist/{chunk-XMCVUYKC.cjs → chunk-6NO4YYU7.cjs} +12 -12
- package/dist/{chunk-XMCVUYKC.cjs.map → chunk-6NO4YYU7.cjs.map} +1 -1
- package/dist/{chunk-QO6MCQYE.cjs → chunk-7CZ4MJSI.cjs} +13 -10
- package/dist/chunk-7CZ4MJSI.cjs.map +1 -0
- package/dist/{chunk-FPE3SQ5D.js → chunk-A7MG4SBL.js} +3 -3
- package/dist/{chunk-SM26C2L7.js → chunk-AZQOX2RS.js} +2 -2
- package/dist/{chunk-JYI4R25Q.js → chunk-DOVBNECK.js} +3 -3
- package/dist/{chunk-7KDR5P23.js → chunk-FQASXKBK.js} +2 -2
- package/dist/{chunk-LFWPWIAC.cjs → chunk-H4W3NCR2.cjs} +7 -7
- package/dist/{chunk-LFWPWIAC.cjs.map → chunk-H4W3NCR2.cjs.map} +1 -1
- package/dist/{chunk-DUQQEQ7G.cjs → chunk-IBIGXER5.cjs} +191 -153
- package/dist/chunk-IBIGXER5.cjs.map +1 -0
- package/dist/{chunk-IZ6A6K6N.cjs → chunk-KNNSS3CZ.cjs} +15 -15
- package/dist/{chunk-IZ6A6K6N.cjs.map → chunk-KNNSS3CZ.cjs.map} +1 -1
- package/dist/{chunk-AGV26PUR.js → chunk-LGMRNYTX.js} +3 -3
- package/dist/{chunk-YZJUHEPZ.js → chunk-LYYVTI34.js} +5 -5
- package/dist/{chunk-TAQMYVO6.cjs → chunk-M6XUXQ5Q.cjs} +40 -40
- package/dist/{chunk-TAQMYVO6.cjs.map → chunk-M6XUXQ5Q.cjs.map} +1 -1
- package/dist/{chunk-2BTDPDME.js → chunk-MG4AMIFX.js} +4 -1
- package/dist/chunk-MG4AMIFX.js.map +1 -0
- package/dist/{chunk-ZQYFDYWV.js → chunk-NQODKJ2F.js} +121 -83
- package/dist/{chunk-ZQYFDYWV.js.map → chunk-NQODKJ2F.js.map} +1 -1
- package/dist/{chunk-KUXGQ2FQ.cjs → chunk-OHKKI6EF.cjs} +32 -32
- package/dist/{chunk-KUXGQ2FQ.cjs.map → chunk-OHKKI6EF.cjs.map} +1 -1
- package/dist/{chunk-JOH5D76X.cjs → chunk-PRJFSF4W.cjs} +73 -73
- package/dist/{chunk-JOH5D76X.cjs.map → chunk-PRJFSF4W.cjs.map} +1 -1
- package/dist/{chunk-W3JZXLJ2.js → chunk-PVMPBWKX.js} +6 -6
- package/dist/{chunk-7KNOHGMI.js → chunk-RQVR7I73.js} +2 -2
- package/dist/{chunk-7QP65IE4.cjs → chunk-RYKANQE6.cjs} +5 -5
- package/dist/{chunk-7QP65IE4.cjs.map → chunk-RYKANQE6.cjs.map} +1 -1
- package/dist/{chunk-QAG357PE.js → chunk-UXJ47OJF.js} +2 -2
- package/dist/{chunk-JNC4LVD6.cjs → chunk-VACXEUGF.cjs} +29 -29
- package/dist/{chunk-JNC4LVD6.cjs.map → chunk-VACXEUGF.cjs.map} +1 -1
- package/dist/{chunk-LUKRPHEW.cjs → chunk-YPFQ2UIH.cjs} +73 -73
- package/dist/{chunk-LUKRPHEW.cjs.map → chunk-YPFQ2UIH.cjs.map} +1 -1
- package/dist/{chunk-IPEIOR77.cjs → chunk-ZVAAIP5Y.cjs} +40 -40
- package/dist/{chunk-IPEIOR77.cjs.map → chunk-ZVAAIP5Y.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +5 -5
- package/dist/components/faq/index.js +4 -4
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/features/policy-configuration-panel.d.ts +11 -0
- package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +135 -135
- package/dist/components/index.js +11 -11
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +7 -7
- package/dist/components/onboarding-guides/index.js +6 -6
- package/dist/components/related-content/index.cjs +5 -5
- package/dist/components/related-content/index.js +4 -4
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.js +2 -2
- package/dist/types/permissions.d.ts +9 -6
- package/dist/types/permissions.d.ts.map +1 -1
- package/dist/utils/embed-authed-fetch.d.ts +19 -3
- package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
- package/dist/utils/index.cjs +3 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +6 -1
- package/src/components/features/policy-configuration-panel.tsx +185 -122
- package/src/stories/PolicyConfigurationPanel.stories.tsx +254 -0
- package/src/types/permissions.ts +10 -7
- package/src/utils/__tests__/embed-authed-fetch.test.ts +39 -0
- package/src/utils/embed-authed-fetch.ts +27 -3
- package/dist/chunk-2BTDPDME.js.map +0 -1
- package/dist/chunk-DUQQEQ7G.cjs.map +0 -1
- package/dist/chunk-QO6MCQYE.cjs.map +0 -1
- /package/dist/{chunk-63TSTDME.js.map → chunk-3ZE55IH6.js.map} +0 -0
- /package/dist/{chunk-Y3NFB6HR.js.map → chunk-5U6OZ5FD.js.map} +0 -0
- /package/dist/{chunk-IAZ5AN2E.js.map → chunk-6IPNXMB7.js.map} +0 -0
- /package/dist/{chunk-FPE3SQ5D.js.map → chunk-A7MG4SBL.js.map} +0 -0
- /package/dist/{chunk-SM26C2L7.js.map → chunk-AZQOX2RS.js.map} +0 -0
- /package/dist/{chunk-JYI4R25Q.js.map → chunk-DOVBNECK.js.map} +0 -0
- /package/dist/{chunk-7KDR5P23.js.map → chunk-FQASXKBK.js.map} +0 -0
- /package/dist/{chunk-AGV26PUR.js.map → chunk-LGMRNYTX.js.map} +0 -0
- /package/dist/{chunk-YZJUHEPZ.js.map → chunk-LYYVTI34.js.map} +0 -0
- /package/dist/{chunk-W3JZXLJ2.js.map → chunk-PVMPBWKX.js.map} +0 -0
- /package/dist/{chunk-7KNOHGMI.js.map → chunk-RQVR7I73.js.map} +0 -0
- /package/dist/{chunk-QAG357PE.js.map → chunk-UXJ47OJF.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamingo-stack/openframe-frontend-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.442-snapshot.20260717134227",
|
|
4
4
|
"description": "Shared design system and components for all Flamingo platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -267,6 +267,10 @@
|
|
|
267
267
|
"yalc:push": "yalc push ${YALC_STORE_FOLDER:+--store-folder \"$YALC_STORE_FOLDER\"}",
|
|
268
268
|
"yalc:publish": "yalc publish ${YALC_STORE_FOLDER:+--store-folder \"$YALC_STORE_FOLDER\"}",
|
|
269
269
|
"generate:icons": "node scripts/generate-icons.mjs",
|
|
270
|
+
"lint:biome": "biome check .",
|
|
271
|
+
"lint:biome:fix": "biome check --fix .",
|
|
272
|
+
"format": "biome format .",
|
|
273
|
+
"format:fix": "biome format --fix .",
|
|
270
274
|
"storybook": "storybook dev -p 6006",
|
|
271
275
|
"build-storybook": "storybook build"
|
|
272
276
|
},
|
|
@@ -362,6 +366,7 @@
|
|
|
362
366
|
"vaul": "^1.1.2"
|
|
363
367
|
},
|
|
364
368
|
"devDependencies": {
|
|
369
|
+
"@biomejs/biome": "2.4.4",
|
|
365
370
|
"@hookform/resolvers": "^5.4.0",
|
|
366
371
|
"@storybook/nextjs-vite": "^10.1.11",
|
|
367
372
|
"@svgr/cli": "^8.1.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useRef, useEffect, useState } from 'react'
|
|
5
|
-
import { ChevronDown } from 'lucide-react'
|
|
5
|
+
import { ChevronDown, Shield } from 'lucide-react'
|
|
6
6
|
import { cn } from '../../utils/cn'
|
|
7
7
|
import { ToolIcon } from '../tool-icon'
|
|
8
8
|
import { Button } from '../ui/button'
|
|
@@ -34,62 +34,110 @@ const getApprovalLevelLabel = (level: ApprovalLevel | undefined, editMode: boole
|
|
|
34
34
|
return option?.label || level
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const ApprovalLevelDropdown: React.FC<{
|
|
38
|
+
value: ApprovalLevel | undefined;
|
|
39
|
+
onChange: (level: ApprovalLevel | undefined) => void;
|
|
40
|
+
/** Adds the "Clear Global Permission" item (category bulk dropdown). */
|
|
41
|
+
allowClear?: boolean;
|
|
42
|
+
triggerClassName?: string;
|
|
43
|
+
contentClassName?: string;
|
|
44
|
+
}> = ({ value, onChange, allowClear = false, triggerClassName, contentClassName }) => (
|
|
45
|
+
<DropdownMenu>
|
|
46
|
+
<DropdownMenuTrigger asChild>
|
|
47
|
+
<Button
|
|
48
|
+
variant="outline"
|
|
49
|
+
rightIcon={<ChevronDown className="h-6 w-6 text-ods-text-secondary" />}
|
|
50
|
+
className={cn(
|
|
51
|
+
"!text-h6 bg-ods-card border border-ods-border rounded-md flex gap-[var(--spacing-system-xsf)] items-center justify-between !px-[var(--spacing-system-xsf)] py-[var(--spacing-system-xsf)] hover:bg-ods-bg-hover transition-colors h-auto",
|
|
52
|
+
value ? "text-ods-text-primary" : "text-ods-text-secondary",
|
|
53
|
+
triggerClassName
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{getApprovalLevelLabel(value, true)}
|
|
57
|
+
</Button>
|
|
58
|
+
</DropdownMenuTrigger>
|
|
59
|
+
<DropdownMenuContent align="end" className={contentClassName}>
|
|
60
|
+
{allowClear && (
|
|
61
|
+
<DropdownMenuItem onClick={() => onChange(undefined)} className="text-h6">
|
|
62
|
+
Clear Global Permission
|
|
63
|
+
</DropdownMenuItem>
|
|
64
|
+
)}
|
|
65
|
+
{approvalLevelOptions.map(option => (
|
|
66
|
+
<DropdownMenuItem
|
|
67
|
+
key={option.value}
|
|
68
|
+
onClick={() => onChange(option.value)}
|
|
69
|
+
className="text-h6"
|
|
70
|
+
>
|
|
71
|
+
{option.label}
|
|
72
|
+
</DropdownMenuItem>
|
|
73
|
+
))}
|
|
74
|
+
</DropdownMenuContent>
|
|
75
|
+
</DropdownMenu>
|
|
76
|
+
)
|
|
77
|
+
|
|
37
78
|
const PolicyRow: React.FC<{
|
|
38
79
|
policy: PermissionPolicy;
|
|
39
80
|
categoryId: string;
|
|
40
81
|
editMode: boolean;
|
|
41
82
|
onPermissionChange: (categoryId: string, policyId: string, level: ApprovalLevel) => void;
|
|
42
83
|
}> = ({ policy, categoryId, editMode, onPermissionChange }) => {
|
|
84
|
+
const handleChange = (level: ApprovalLevel | undefined) => {
|
|
85
|
+
if (level) onPermissionChange(categoryId, policy.id, level)
|
|
86
|
+
}
|
|
87
|
+
|
|
43
88
|
return (
|
|
44
|
-
<div className="bg-ods-bg border-b border-ods-border flex gap-
|
|
89
|
+
<div className="bg-ods-bg border-b last:border-b-0 border-ods-border flex gap-[var(--spacing-system-m)] items-start md:items-center px-[var(--spacing-system-m)] py-[var(--spacing-system-sf)]">
|
|
45
90
|
{/* Tool Icon */}
|
|
46
|
-
<div className="bg-ods-bg border border-ods-border rounded-md flex items-center justify-center w-8 h-8">
|
|
91
|
+
<div className="bg-ods-bg border border-ods-border rounded-md flex items-center justify-center w-8 h-8 shrink-0">
|
|
47
92
|
<ToolIcon toolType={policy.toolName} size={16} />
|
|
48
93
|
</div>
|
|
49
94
|
|
|
50
|
-
{/* Policy Info */}
|
|
95
|
+
{/* Policy Info — on mobile the approval level stacks below the pattern */}
|
|
51
96
|
<div className="flex-1 flex flex-col min-w-0">
|
|
52
|
-
<p className="text-
|
|
97
|
+
<p className="text-h4 text-ods-text-primary truncate" title={policy.name}>
|
|
53
98
|
{policy.name}
|
|
54
99
|
</p>
|
|
55
|
-
<p
|
|
100
|
+
<p
|
|
101
|
+
className="text-h6 text-ods-text-secondary truncate md:whitespace-normal md:break-all"
|
|
102
|
+
title={policy.commandPattern}
|
|
103
|
+
>
|
|
56
104
|
{policy.commandPattern}
|
|
57
105
|
</p>
|
|
106
|
+
{editMode ? (
|
|
107
|
+
<div className="md:hidden mt-[var(--spacing-system-xsf)]">
|
|
108
|
+
<ApprovalLevelDropdown
|
|
109
|
+
value={policy.approvalLevel}
|
|
110
|
+
onChange={handleChange}
|
|
111
|
+
triggerClassName="w-full"
|
|
112
|
+
contentClassName="w-[180px]"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
) : policy.approvalLevel ? (
|
|
116
|
+
<span className="md:hidden text-h4 text-ods-text-primary">
|
|
117
|
+
{getApprovalLevelLabel(policy.approvalLevel)}
|
|
118
|
+
</span>
|
|
119
|
+
) : null}
|
|
58
120
|
</div>
|
|
59
121
|
|
|
60
|
-
{/* Approval Level
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{option.label}
|
|
80
|
-
</DropdownMenuItem>
|
|
81
|
-
))}
|
|
82
|
-
</DropdownMenuContent>
|
|
83
|
-
</DropdownMenu>
|
|
84
|
-
) : policy.approvalLevel ? (
|
|
85
|
-
<div className="px-3 py-2 w-[180px]">
|
|
86
|
-
<span className="text-h6 text-ods-text-primary">
|
|
87
|
-
{getApprovalLevelLabel(policy.approvalLevel, editMode)}
|
|
88
|
-
</span>
|
|
89
|
-
</div>
|
|
90
|
-
) : (
|
|
91
|
-
<div className="px-3 py-2 w-[180px]" />
|
|
92
|
-
)}
|
|
122
|
+
{/* Approval Level column (desktop only) */}
|
|
123
|
+
<div className="hidden md:block shrink-0">
|
|
124
|
+
{editMode ? (
|
|
125
|
+
<ApprovalLevelDropdown
|
|
126
|
+
value={policy.approvalLevel}
|
|
127
|
+
onChange={handleChange}
|
|
128
|
+
triggerClassName="w-[180px]"
|
|
129
|
+
contentClassName="w-[180px]"
|
|
130
|
+
/>
|
|
131
|
+
) : (
|
|
132
|
+
<div className="px-[var(--spacing-system-sf)] py-[var(--spacing-system-xsf)] w-[180px]">
|
|
133
|
+
{policy.approvalLevel && (
|
|
134
|
+
<span className="text-h4 text-ods-text-primary">
|
|
135
|
+
{getApprovalLevelLabel(policy.approvalLevel)}
|
|
136
|
+
</span>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
93
141
|
</div>
|
|
94
142
|
)
|
|
95
143
|
}
|
|
@@ -97,7 +145,7 @@ const PolicyRow: React.FC<{
|
|
|
97
145
|
const useAnimatedHeight = (isExpanded: boolean) => {
|
|
98
146
|
const [height, setHeight] = useState<number | 'auto'>(0);
|
|
99
147
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
100
|
-
|
|
148
|
+
|
|
101
149
|
useEffect(() => {
|
|
102
150
|
if (contentRef.current) {
|
|
103
151
|
const contentHeight = contentRef.current.scrollHeight;
|
|
@@ -111,93 +159,73 @@ const useAnimatedHeight = (isExpanded: boolean) => {
|
|
|
111
159
|
const CategorySection: React.FC<{
|
|
112
160
|
category: PermissionCategory;
|
|
113
161
|
editMode: boolean;
|
|
114
|
-
|
|
115
|
-
|
|
162
|
+
isExpanded: boolean;
|
|
163
|
+
/** Last bulk level applied to this category (display-only, edit mode). */
|
|
164
|
+
bulkLevel: ApprovalLevel | undefined;
|
|
165
|
+
onToggle: (categoryId: string) => void;
|
|
166
|
+
onBulkLevelChange: (categoryId: string, level: ApprovalLevel | undefined) => void;
|
|
116
167
|
onPolicyPermissionChange: (categoryId: string, policyId: string, level: ApprovalLevel) => void;
|
|
117
|
-
}> = ({
|
|
118
|
-
category,
|
|
119
|
-
editMode,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
}> = ({
|
|
169
|
+
category,
|
|
170
|
+
editMode,
|
|
171
|
+
isExpanded,
|
|
172
|
+
bulkLevel,
|
|
173
|
+
onToggle,
|
|
174
|
+
onBulkLevelChange,
|
|
175
|
+
onPolicyPermissionChange
|
|
123
176
|
}) => {
|
|
124
|
-
const { contentRef, height } = useAnimatedHeight(
|
|
125
|
-
|
|
177
|
+
const { contentRef, height } = useAnimatedHeight(isExpanded);
|
|
178
|
+
const handleBulkChange = (level: ApprovalLevel | undefined) => onBulkLevelChange(category.id, level)
|
|
179
|
+
|
|
126
180
|
return (
|
|
127
181
|
<>
|
|
128
182
|
{/* Category Header */}
|
|
129
|
-
<div
|
|
130
|
-
className="bg-ods-card border-t border-ods-border flex gap-
|
|
131
|
-
onClick={() =>
|
|
183
|
+
<div
|
|
184
|
+
className="bg-ods-card border-t first:border-t-0 border-ods-border flex gap-[var(--spacing-system-m)] items-center pl-[var(--spacing-system-m)] pr-[var(--spacing-system-xsf)] py-[var(--spacing-system-s)] cursor-pointer hover:bg-ods-bg-hover transition-colors"
|
|
185
|
+
onClick={() => onToggle(category.id)}
|
|
132
186
|
>
|
|
133
187
|
{/* Category Icon */}
|
|
134
|
-
<div className="bg-ods-bg border border-ods-border rounded-md flex items-center justify-center w-8 h-8">
|
|
188
|
+
<div className="bg-ods-bg border border-ods-border rounded-md flex items-center justify-center w-8 h-8 shrink-0">
|
|
135
189
|
<div className="text-ods-text-secondary">
|
|
136
|
-
{category.icon}
|
|
190
|
+
{category.icon ?? <Shield className="w-4 h-4" />}
|
|
137
191
|
</div>
|
|
138
192
|
</div>
|
|
139
193
|
|
|
140
|
-
{/* Category Info */}
|
|
141
|
-
<div className="flex-1 flex flex-col">
|
|
142
|
-
<p className="
|
|
194
|
+
{/* Category Info — on mobile the bulk dropdown stacks below the count */}
|
|
195
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
196
|
+
<p className="text-h4 text-ods-text-primary truncate" title={category.name}>
|
|
143
197
|
{category.name}
|
|
144
198
|
</p>
|
|
145
199
|
<p className="text-h6 text-ods-text-secondary">
|
|
146
|
-
{category.
|
|
200
|
+
{category.policies.length} Configurations
|
|
147
201
|
</p>
|
|
202
|
+
{editMode && (
|
|
203
|
+
<div className="md:hidden mt-[var(--spacing-system-xsf)]" onClick={(e) => e.stopPropagation()}>
|
|
204
|
+
<ApprovalLevelDropdown
|
|
205
|
+
value={bulkLevel}
|
|
206
|
+
onChange={handleBulkChange}
|
|
207
|
+
allowClear
|
|
208
|
+
triggerClassName="w-full"
|
|
209
|
+
contentClassName="w-[256px]"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
148
213
|
</div>
|
|
149
214
|
|
|
150
|
-
{/* Global Permission
|
|
215
|
+
{/* Global Permission column (desktop only) */}
|
|
151
216
|
{editMode ? (
|
|
152
|
-
<div onClick={(e) => e.stopPropagation()}>
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<span className={cn(
|
|
161
|
-
"md:!text-sm font-medium",
|
|
162
|
-
category.globalPermission
|
|
163
|
-
? "text-ods-text-primary"
|
|
164
|
-
: "text-ods-text-secondary"
|
|
165
|
-
)}>
|
|
166
|
-
{getApprovalLevelLabel(category.globalPermission, editMode)}
|
|
167
|
-
</span>
|
|
168
|
-
</Button>
|
|
169
|
-
</DropdownMenuTrigger>
|
|
170
|
-
<DropdownMenuContent align="end" className="w-[256px]">
|
|
171
|
-
<DropdownMenuItem
|
|
172
|
-
onClick={() => onGlobalPermissionChange(category.id, undefined)}
|
|
173
|
-
className="md:!text-sm"
|
|
174
|
-
>
|
|
175
|
-
Clear Global Permission
|
|
176
|
-
</DropdownMenuItem>
|
|
177
|
-
{approvalLevelOptions.map(option => (
|
|
178
|
-
<DropdownMenuItem
|
|
179
|
-
key={option.value}
|
|
180
|
-
onClick={() => onGlobalPermissionChange(category.id, option.value)}
|
|
181
|
-
className="md:!text-sm"
|
|
182
|
-
>
|
|
183
|
-
{option.label}
|
|
184
|
-
</DropdownMenuItem>
|
|
185
|
-
))}
|
|
186
|
-
</DropdownMenuContent>
|
|
187
|
-
</DropdownMenu>
|
|
188
|
-
</div>
|
|
189
|
-
) : category.globalPermission ? (
|
|
190
|
-
<div
|
|
191
|
-
className="px-3 py-2 w-[256px]"
|
|
192
|
-
onClick={(e) => e.stopPropagation()}
|
|
193
|
-
>
|
|
194
|
-
<span className="md:!text-sm font-medium text-ods-text-primary">
|
|
195
|
-
{getApprovalLevelLabel(category.globalPermission, editMode)}
|
|
196
|
-
</span>
|
|
217
|
+
<div className="hidden md:block shrink-0" onClick={(e) => e.stopPropagation()}>
|
|
218
|
+
<ApprovalLevelDropdown
|
|
219
|
+
value={bulkLevel}
|
|
220
|
+
onChange={handleBulkChange}
|
|
221
|
+
allowClear
|
|
222
|
+
triggerClassName="w-[256px]"
|
|
223
|
+
contentClassName="w-[256px]"
|
|
224
|
+
/>
|
|
197
225
|
</div>
|
|
198
226
|
) : (
|
|
199
|
-
<div
|
|
200
|
-
className="px-
|
|
227
|
+
<div
|
|
228
|
+
className="hidden md:block px-[var(--spacing-system-sf)] py-[var(--spacing-system-xsf)] w-[256px] shrink-0"
|
|
201
229
|
onClick={(e) => e.stopPropagation()}
|
|
202
230
|
/>
|
|
203
231
|
)}
|
|
@@ -208,32 +236,32 @@ const CategorySection: React.FC<{
|
|
|
208
236
|
variant="transparent"
|
|
209
237
|
onClick={(e) => {
|
|
210
238
|
e.stopPropagation();
|
|
211
|
-
|
|
239
|
+
onToggle(category.id);
|
|
212
240
|
}}
|
|
213
|
-
leftIcon={<ChevronDown
|
|
241
|
+
leftIcon={<ChevronDown
|
|
214
242
|
className={cn(
|
|
215
243
|
"h-6 w-6 text-ods-text-primary transition-transform duration-300",
|
|
216
|
-
|
|
217
|
-
)}
|
|
244
|
+
isExpanded && "rotate-180"
|
|
245
|
+
)}
|
|
218
246
|
/>}
|
|
219
|
-
className="rounded-md h-auto w-auto"
|
|
220
|
-
aria-label={
|
|
247
|
+
className="rounded-md h-auto w-auto shrink-0"
|
|
248
|
+
aria-label={isExpanded ? "Collapse" : "Expand"}
|
|
221
249
|
/>
|
|
222
250
|
</div>
|
|
223
251
|
|
|
224
252
|
{/* Policies List */}
|
|
225
|
-
<div
|
|
253
|
+
<div
|
|
226
254
|
ref={contentRef}
|
|
227
|
-
style={{
|
|
255
|
+
style={{
|
|
228
256
|
height: height === 'auto' ? 'auto' : `${height}px`,
|
|
229
257
|
transition: 'height 300ms ease-in-out, opacity 300ms ease-in-out'
|
|
230
258
|
}}
|
|
231
259
|
className={cn(
|
|
232
260
|
"overflow-hidden",
|
|
233
|
-
|
|
261
|
+
isExpanded ? "opacity-100" : "opacity-0"
|
|
234
262
|
)}
|
|
235
263
|
>
|
|
236
|
-
<div className="px-
|
|
264
|
+
<div className="px-[var(--spacing-system-mf)] pb-[var(--spacing-system-mf)] pt-0">
|
|
237
265
|
<div className="border border-ods-border rounded-md overflow-hidden">
|
|
238
266
|
{category.policies.map((policy) => (
|
|
239
267
|
<PolicyRow
|
|
@@ -251,14 +279,47 @@ const CategorySection: React.FC<{
|
|
|
251
279
|
)
|
|
252
280
|
}
|
|
253
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Grouped list of permission categories with per-policy approval dropdowns.
|
|
284
|
+
* Presentation state — which categories are expanded and the last bulk level
|
|
285
|
+
* picked per category — lives inside the panel, so callers pass pure data and
|
|
286
|
+
* background data refreshes never collapse the user's view. Bulk selections
|
|
287
|
+
* reset when `editMode` changes.
|
|
288
|
+
*
|
|
289
|
+
* Responsive: below `md` the approval level / dropdowns stack inside the text
|
|
290
|
+
* column (per the ODS mobile design); at `md+` they render as fixed-width
|
|
291
|
+
* right-hand columns.
|
|
292
|
+
*/
|
|
254
293
|
export const PolicyConfigurationPanel: React.FC<PolicyConfigurationPanelProps> = ({
|
|
255
294
|
categories,
|
|
256
295
|
editMode,
|
|
257
|
-
onCategoryToggle,
|
|
258
|
-
onGlobalPermissionChange,
|
|
259
296
|
onPolicyPermissionChange,
|
|
297
|
+
onCategoryPermissionChange,
|
|
260
298
|
className
|
|
261
299
|
}) => {
|
|
300
|
+
const [expandedIds, setExpandedIds] = useState<ReadonlySet<string>>(new Set())
|
|
301
|
+
const [bulkLevels, setBulkLevels] = useState<Record<string, ApprovalLevel | undefined>>({})
|
|
302
|
+
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
setBulkLevels({})
|
|
305
|
+
}, [editMode])
|
|
306
|
+
|
|
307
|
+
const handleToggle = (categoryId: string) => {
|
|
308
|
+
setExpandedIds(prev => {
|
|
309
|
+
const next = new Set(prev)
|
|
310
|
+
if (next.has(categoryId)) next.delete(categoryId)
|
|
311
|
+
else next.add(categoryId)
|
|
312
|
+
return next
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const handleBulkLevelChange = (categoryId: string, level: ApprovalLevel | undefined) => {
|
|
317
|
+
setBulkLevels(prev => ({ ...prev, [categoryId]: level }))
|
|
318
|
+
if (level) {
|
|
319
|
+
onCategoryPermissionChange(categoryId, level)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
262
323
|
return (
|
|
263
324
|
<div className={cn(
|
|
264
325
|
"bg-ods-card border border-ods-border rounded-md overflow-hidden",
|
|
@@ -269,8 +330,10 @@ export const PolicyConfigurationPanel: React.FC<PolicyConfigurationPanelProps> =
|
|
|
269
330
|
key={category.id}
|
|
270
331
|
category={category}
|
|
271
332
|
editMode={editMode}
|
|
272
|
-
|
|
273
|
-
|
|
333
|
+
isExpanded={expandedIds.has(category.id)}
|
|
334
|
+
bulkLevel={bulkLevels[category.id]}
|
|
335
|
+
onToggle={handleToggle}
|
|
336
|
+
onBulkLevelChange={handleBulkLevelChange}
|
|
274
337
|
onPolicyPermissionChange={onPolicyPermissionChange}
|
|
275
338
|
/>
|
|
276
339
|
))}
|
|
@@ -278,4 +341,4 @@ export const PolicyConfigurationPanel: React.FC<PolicyConfigurationPanelProps> =
|
|
|
278
341
|
)
|
|
279
342
|
}
|
|
280
343
|
|
|
281
|
-
PolicyConfigurationPanel.displayName = 'PolicyConfigurationPanel'
|
|
344
|
+
PolicyConfigurationPanel.displayName = 'PolicyConfigurationPanel'
|