From c96f1ef2e831cd040563cac25b047ab83a719985 Mon Sep 17 00:00:00 2001 From: dhruveshmishra Date: Wed, 5 Aug 2026 22:50:19 +0530 Subject: [PATCH 1/4] fix: make PermissionShield tooltip placement adaptive to viewport (#1772) Signed-off-by: dhruveshmishra --- src/custom/permissions.tsx | 53 +++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/custom/permissions.tsx b/src/custom/permissions.tsx index 2e0952c16..26f4f949e 100644 --- a/src/custom/permissions.tsx +++ b/src/custom/permissions.tsx @@ -72,6 +72,12 @@ export interface PermissionShieldProps { permissionKey: PermissionKeySpec; children: React.ReactNode; variant?: 'inline' | 'badge'; + boundaryPadding?: { + top?: number; + left?: number; + right?: number; + bottom?: number; + }; } /** Distinct, defined values in first-seen order — used for the metadata chips. */ @@ -93,7 +99,8 @@ const uniqueDefined = (values: (string | undefined)[]): string[] => export const PermissionShield: React.FC = ({ permissionKey, children, - variant = 'inline' + variant = 'inline', + boundaryPadding }) => { const [open, setOpen] = React.useState(false); const [copiedKeyId, setCopiedKeyId] = React.useState(null); @@ -101,6 +108,9 @@ export const PermissionShield: React.FC = ({ const userContext = usePermissionUserContext(); const unmetKeys = useUnmetPermissionKeys(permissionKey); + const defaultPadding = { top: 85, left: 270, right: 8, bottom: 8 }; + const padding = boundaryPadding ? { ...defaultPadding, ...boundaryPadding } : defaultPadding; + const handleClose = () => { setOpen(false); }; @@ -419,24 +429,29 @@ export const PermissionShield: React.FC = ({ disableTouchListener slotProps={{ popper: { - modifiers: [ - { - name: 'flip', - enabled: true, - options: { - fallbackPlacements: ['bottom', 'right', 'left'] - } - }, - { - name: 'preventOverflow', - enabled: true, - options: { - boundary: 'viewport', - altAxis: true, - padding: 8 + popperOptions: { + modifiers: [ + { + name: 'flip', + enabled: true, + options: { + boundary: 'viewport', + fallbackPlacements: ['right', 'bottom', 'left'], + padding + } + }, + { + name: 'preventOverflow', + enabled: true, + options: { + boundary: 'viewport', + tether: false, + altAxis: true, + padding + } } - } - ] + ] + } }, tooltip: { sx: { @@ -444,6 +459,8 @@ export const PermissionShield: React.FC = ({ color: '#FFFFFF', maxWidth: 360, minWidth: 300, + maxHeight: '320px', + overflowY: 'auto', padding: '12px', borderLeft: '4px solid #EBC024', borderRadius: '8px', From fe678a9d357511355ab3d152e2921cb833182048 Mon Sep 17 00:00:00 2001 From: dhruveshmishra Date: Mon, 10 Aug 2026 12:14:03 +0530 Subject: [PATCH 2/4] fix: make PermissionShield tooltip sizing and boundary padding responsive Signed-off-by: dhruveshmishra --- src/custom/permissions.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/custom/permissions.tsx b/src/custom/permissions.tsx index 26f4f949e..4380c1fac 100644 --- a/src/custom/permissions.tsx +++ b/src/custom/permissions.tsx @@ -2,6 +2,7 @@ import { Key } from '@meshery/schemas/permissions'; import KeyIcon from '@mui/icons-material/Key'; import LaunchIcon from '@mui/icons-material/Launch'; import SecurityIcon from '@mui/icons-material/Security'; +import { useMediaQuery, useTheme } from '@mui/material'; import React from 'react'; import type { MissingCapabilityReason, @@ -108,7 +109,11 @@ export const PermissionShield: React.FC = ({ const userContext = usePermissionUserContext(); const unmetKeys = useUnmetPermissionKeys(permissionKey); - const defaultPadding = { top: 85, left: 270, right: 8, bottom: 8 }; + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + const defaultPadding = isMobile + ? { top: 8, left: 8, right: 8, bottom: 8 } + : { top: 85, left: 270, right: 8, bottom: 8 }; const padding = boundaryPadding ? { ...defaultPadding, ...boundaryPadding } : defaultPadding; const handleClose = () => { @@ -457,8 +462,8 @@ export const PermissionShield: React.FC = ({ sx: { background: '#1A1A1A', color: '#FFFFFF', - maxWidth: 360, - minWidth: 300, + maxWidth: { xs: 'calc(100vw - 32px)', sm: 360 }, + minWidth: { xs: 'auto', sm: 300 }, maxHeight: '320px', overflowY: 'auto', padding: '12px', From ec8656bda88a04de728c1d643ec8418729bfdbb3 Mon Sep 17 00:00:00 2001 From: dhruveshmishra Date: Tue, 11 Aug 2026 01:58:00 +0530 Subject: [PATCH 3/4] fix: adjust PermissionShield tooltip boundaries, responsive sizing, and zIndex Signed-off-by: dhruveshmishra --- src/custom/permissions.tsx | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/custom/permissions.tsx b/src/custom/permissions.tsx index 4380c1fac..5d9fe1d51 100644 --- a/src/custom/permissions.tsx +++ b/src/custom/permissions.tsx @@ -112,8 +112,8 @@ export const PermissionShield: React.FC = ({ const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const defaultPadding = isMobile - ? { top: 8, left: 8, right: 8, bottom: 8 } - : { top: 85, left: 270, right: 8, bottom: 8 }; + ? { top: 64, left: 8, right: 8, bottom: 8 } + : { top: 85, left: 16, right: 8, bottom: 8 }; const padding = boundaryPadding ? { ...defaultPadding, ...boundaryPadding } : defaultPadding; const handleClose = () => { @@ -172,7 +172,7 @@ export const PermissionShield: React.FC = ({ {/* Title: AUTHORIZATION REQUIRED — medium gray */} = ({ {/* Subtitle */} @@ -230,7 +230,7 @@ export const PermissionShield: React.FC = ({ = ({ {/* Description — italicized, equal padding both sides, no divider from key name */} - + @@ -426,7 +426,7 @@ export const PermissionShield: React.FC = ({ = ({ disableTouchListener slotProps={{ popper: { + style: { zIndex: 1100 }, popperOptions: { modifiers: [ { @@ -441,7 +442,7 @@ export const PermissionShield: React.FC = ({ enabled: true, options: { boundary: 'viewport', - fallbackPlacements: ['right', 'bottom', 'left'], + fallbackPlacements: ['bottom-start', 'top-start'], padding } }, @@ -462,11 +463,11 @@ export const PermissionShield: React.FC = ({ sx: { background: '#1A1A1A', color: '#FFFFFF', - maxWidth: { xs: 'calc(100vw - 32px)', sm: 360 }, - minWidth: { xs: 'auto', sm: 300 }, - maxHeight: '320px', + maxWidth: { xs: 260, sm: 360 }, + minWidth: { xs: 240, sm: 300 }, + maxHeight: { xs: '200px', sm: '320px' }, overflowY: 'auto', - padding: '12px', + padding: { xs: '8px', sm: '12px' }, borderLeft: '4px solid #EBC024', borderRadius: '8px', boxShadow: '0 8px 32px rgba(0,0,0,0.4)' From 86521b17aedb69cc75420efea7ee5231b052031f Mon Sep 17 00:00:00 2001 From: dhruveshmishra Date: Wed, 12 Aug 2026 02:02:51 +0530 Subject: [PATCH 4/4] fix(PermissionShield): fix broken merge and adjust tooltip responsive sizing and zIndex Signed-off-by: dhruveshmishra --- src/custom/permissions.tsx | 173 +++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 95 deletions(-) diff --git a/src/custom/permissions.tsx b/src/custom/permissions.tsx index 86954d157..858562dcf 100644 --- a/src/custom/permissions.tsx +++ b/src/custom/permissions.tsx @@ -124,55 +124,40 @@ export interface PermissionSessionContextProps { * resolves everything internally — same logic as `PermissionShield`. * 2. **Pre-resolved** (`displayedKeys` etc.): pass already-resolved data. */ -export const PermissionShield: React.FC = ({ - permissionKey, - children, - variant = 'inline', - boundaryPadding +export const PermissionSessionContext: React.FC = ({ + variant = 'tooltip', + permissionKey: permissionKeyProp, + displayedKeys: displayedKeysProp, + subtitle: subtitleProp, + categories: categoriesProp, + subcategories: subcategoriesProp }) => { const [copiedKeyId, setCopiedKeyId] = React.useState(null); const userContext = usePermissionUserContext(); - const unmetKeys = useUnmetPermissionKeys(permissionKey); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const defaultPadding = isMobile - ? { top: 64, left: 8, right: 8, bottom: 8 } - : { top: 85, left: 16, right: 8, bottom: 8 }; - const padding = boundaryPadding ? { ...defaultPadding, ...boundaryPadding } : defaultPadding; - const handleClose = () => { - setOpen(false); - }; - - const handleToggle = (e: React.MouseEvent) => { - e.stopPropagation(); - setOpen((prev) => { - const next = !prev; - if (next) { - window.dispatchEvent( - new CustomEvent('permission-shield-opened', { detail: { id: uniqueId } }) - ); - } - return next; - }); - }; - - React.useEffect(() => { - const handleOtherOpen = (e: Event) => { - const customEvent = e as CustomEvent; - if (customEvent.detail?.id !== uniqueId) { - setOpen(false); - } - }; - window.addEventListener('permission-shield-opened', handleOtherOpen); - return () => { - window.removeEventListener('permission-shield-opened', handleOtherOpen); - }; - }, [uniqueId]); - - if (!permissionKey) { - return <>{children}; + // Self-resolve when permissionKey is provided (same logic as PermissionShield) + const unmetKeys = useUnmetPermissionKeys(permissionKeyProp); + const selfResolved = !!permissionKeyProp; + + let displayedKeys = displayedKeysProp; + let subtitle = subtitleProp; + let categories = categoriesProp; + let subcategories = subcategoriesProp; + + if (selfResolved) { + const declaredKeys = getPermissionKeys(permissionKeyProp); + displayedKeys = unmetKeys.length > 0 ? unmetKeys : declaredKeys; + const combinator = getPermissionKeyCombinator(permissionKeyProp); + const keyNames = displayedKeys.map((key) => key.function || 'Access Restricted').join(', '); + subtitle = + combinator === 'anyOf' && keyNames + ? `Needs any of: ${keyNames}` + : combinator === 'allOf' && keyNames + ? `Needs all of: ${keyNames}` + : 'Missing requisite key'; + categories = uniqueDefined(displayedKeys.map((key) => key.category)); + subcategories = uniqueDefined(displayedKeys.map((key) => key.subcategory)); } const isCard = variant === 'card'; @@ -228,7 +213,7 @@ export const PermissionShield: React.FC = ({ {/* Title: AUTHORIZATION REQUIRED */} = ({ {/* Subtitle */} - - {subtitle} - + {subtitle && ( + + {subtitle} + + )} {/* Divider */} {(hasKeys || subtitle) && } @@ -289,7 +276,11 @@ export const PermissionShield: React.FC = ({ sx={{ display: 'inline-flex', cursor: 'pointer', - color: copied ? palette.accent : isCard ? palette.muted : 'rgba(255, 255, 255, 0.7)', + color: copied + ? palette.accent + : isCard + ? palette.muted + : 'rgba(255, 255, 255, 0.7)', transition: 'color 0.2s ease', '&:hover': { color: palette.accent @@ -307,38 +298,27 @@ export const PermissionShield: React.FC = ({ color: palette.keyColor }} > - - - - - {key.function || 'Access Restricted'} - - + {key.function || 'Access Restricted'} + + - {/* Description — italicized, equal padding both sides, no divider from key name */} - - - {key.description || - `Allows you to perform the ${key.function || 'selected'} operation.`} - - - - ); - })} + {/* Description */} + + + {key.description || + `Allows you to perform the ${key.function || 'selected'} operation.`} + + + + ); + })} {/* Divider */} {hasKeys && } @@ -571,11 +551,19 @@ export const PermissionShield: React.FC = ({ export const PermissionShield: React.FC = ({ permissionKey, children, - variant = 'inline' + variant = 'inline', + boundaryPadding }) => { const [open, setOpen] = React.useState(false); const uniqueId = React.useId(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + const defaultPadding = isMobile + ? { top: 64, left: 8, right: 8, bottom: 8 } + : { top: 85, left: 16, right: 8, bottom: 8 }; + const padding = boundaryPadding ? { ...defaultPadding, ...boundaryPadding } : defaultPadding; + const handleClose = () => { setOpen(false); }; @@ -613,12 +601,7 @@ export const PermissionShield: React.FC = ({ // Delegate all key resolution (unmet keys, subtitle, categories, subcategories) // to PermissionSessionContext's self-resolving path. This keeps the tooltip // and card rendering paths in sync — both use the same internal resolution logic. - const tooltipTitle = ( - - ); + const tooltipTitle = ; const isBadge = variant === 'badge'; @@ -832,8 +815,8 @@ export const createCanShow = ( // Re-export PermissionProvider types and hooks export { - PermissionProvider, isPermissionKeySet, + PermissionProvider, useHasPermission, usePermission, usePermissionUserContext,