From dc3e9411fa30c37447d765cdf8b42fd3c2e43608 Mon Sep 17 00:00:00 2001 From: Rishi Raj Date: Tue, 4 Aug 2026 11:32:18 +0530 Subject: [PATCH] seprate-content Signed-off-by: Rishi Raj --- src/custom/PermissionShieldContent.tsx | 352 +++++++++++++++++++++++++ src/custom/permissions.tsx | 279 +------------------- src/index.tsx | 3 + 3 files changed, 361 insertions(+), 273 deletions(-) create mode 100644 src/custom/PermissionShieldContent.tsx diff --git a/src/custom/PermissionShieldContent.tsx b/src/custom/PermissionShieldContent.tsx new file mode 100644 index 000000000..fd3aa3225 --- /dev/null +++ b/src/custom/PermissionShieldContent.tsx @@ -0,0 +1,352 @@ +import { Key } from '@meshery/schemas/permissions'; +import KeyIcon from '@mui/icons-material/Key'; +import LaunchIcon from '@mui/icons-material/Launch'; +import React from 'react'; +import { Box, Chip, Link, Tooltip, Typography } from '../base'; +import { OrgHierarchyIcon } from '../icons/OrgHierarchy'; +import { RoleKeyIcon } from '../icons/RoleKey'; +import { UsersIcon } from '../icons/Users'; +import type { PermissionUserContext } from './PermissionProvider'; +import { usePermissionUserContext } from './PermissionProvider'; + +const DIVIDER_SX = { + height: '1px', + background: 'rgba(255, 255, 255, 0.1)', + my: 1.25 +}; + +/** Monochrome fill used for context icons */ +const CONTEXT_ICON_COLOR = '#9E9E9E'; +const CONTEXT_ICON_SIZE = '14'; + +export interface PermissionShieldContentProps { + /** The permission key the user is missing. */ + permissionKey: Key; + /** + * Explicit user context override. When omitted the component reads from + * `PermissionProvider` via `usePermissionUserContext()`. + */ + userContext?: PermissionUserContext; +} + +/** + * Renders the key-metadata card shown inside `PermissionShield` tooltips. + * + * Extracted so the **same content** can be rendered: + * - as a MUI `Tooltip` title (inside `PermissionShield`), and + * - as a standalone inline card on a permission-denied page. + * + * The component expects to live on a **dark** background (`#1A1A1A` or similar). + * When rendered standalone, wrap it in a container that supplies that background, + * the gold left border, and the shadow — identical to the tooltip's `slotProps.tooltip.sx`. + */ +export const PermissionShieldContent: React.FC = ({ + permissionKey, + userContext: userContextProp +}) => { + const [copied, setCopied] = React.useState(false); + const providerContext = usePermissionUserContext(); + const userContext = userContextProp ?? providerContext; + + return ( + + {/* Title: AUTHORIZATION REQUIRED — medium gray */} + + Authorization Required + + + {/* Subtitle */} + + Missing requisite key + + + {/* Divider */} + + + {/* Key Row: KeyIcon (doubles as copy button) + key name */} + + + { + e.stopPropagation(); + navigator.clipboard.writeText(permissionKey.id || ''); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }} + sx={{ + display: 'inline-flex', + cursor: 'pointer', + color: copied ? '#EBC024' : 'rgba(255, 255, 255, 0.7)', + transition: 'color 0.2s ease', + '&:hover': { + color: '#EBC024' + } + }} + > + + + + + {permissionKey.function || 'Access Restricted'} + + + + {/* Description — italicized, equal padding both sides */} + + + {permissionKey.description || + `Allows you to perform the ${permissionKey.function || 'selected'} operation.`} + + + + {/* Divider */} + + + {/* Bottom row: Category/Subcategory chips (left) + Key Reference link (right) */} + + + {permissionKey.category && ( + + )} + {permissionKey.subcategory && ( + + )} + + e.stopPropagation()} + sx={{ + display: 'inline-flex', + alignItems: 'baseline', + fontSize: '0.75rem', + color: '#EBC024', + textDecoration: 'none', + fontWeight: 600, + '&:hover': { + textDecoration: 'underline' + } + }} + > + Key Reference + + + + + + + {/* User / Org / Role context */} + {userContext && (userContext.userName || userContext.orgName) && ( + <> + + + {userContext.userName && ( + + + + + User + + + + {userContext.userName} + + + )} + + + + + Org + + + + {userContext.orgName || 'Private Org'} + + + + + + + Role(s) + + + + {userContext.roleNames && userContext.roleNames.length > 0 + ? userContext.roleNames.join(', ') + : 'None'} + + + + + Seeing this message in error? Contact your Admins to request access. + + + )} + + ); +}; + +/** + * The container styles that the `PermissionShield` tooltip applies via + * `slotProps.tooltip.sx`. Exported so standalone uses can wrap + * `PermissionShieldContent` in a `` and + * get an identical visual. + */ +export const PERMISSION_SHIELD_CARD_SX = { + background: '#1A1A1A', + color: '#FFFFFF', + maxWidth: 360, + minWidth: 300, + padding: '12px', + borderLeft: '4px solid #EBC024', + borderRadius: '8px', + boxShadow: '0 8px 32px rgba(0,0,0,0.4)' +} as const; diff --git a/src/custom/permissions.tsx b/src/custom/permissions.tsx index 5b277bfbb..756cf62f3 100644 --- a/src/custom/permissions.tsx +++ b/src/custom/permissions.tsx @@ -1,6 +1,4 @@ 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 React from 'react'; import type { @@ -8,23 +6,12 @@ import type { MissingPermissionReason } from '../actors/mesheryExtensionContract'; import { MESHERY_EXTENSION_EVENT } from '../actors/mesheryExtensionContract'; -import { Box, Chip, ClickAwayListener, Link, Tooltip, Typography } from '../base'; -import { OrgHierarchyIcon } from '../icons/OrgHierarchy'; -import { RoleKeyIcon } from '../icons/RoleKey'; -import { UsersIcon } from '../icons/Users'; -import { usePermissionUserContext } from './PermissionProvider'; +import { Box, ClickAwayListener, Tooltip } from '../base'; +import { PERMISSION_SHIELD_CARD_SX, PermissionShieldContent } from './PermissionShieldContent'; +export { PERMISSION_SHIELD_CARD_SX, PermissionShieldContent }; +export type { PermissionShieldContentProps } from './PermissionShieldContent'; export type { Key }; -const DIVIDER_SX = { - height: '1px', - background: 'rgba(255, 255, 255, 0.1)', - my: 1.25 -}; - -/** Monochrome fill used for tooltip-context icons */ -const CONTEXT_ICON_COLOR = '#9E9E9E'; -const CONTEXT_ICON_SIZE = '14'; - export type InvertAction = 'disable' | 'hide'; // These two reason events are published onto whichever bus the caller supplies, @@ -80,9 +67,7 @@ export const PermissionShield: React.FC = ({ variant = 'inline' }) => { const [open, setOpen] = React.useState(false); - const [copied, setCopied] = React.useState(false); const uniqueId = React.useId(); - const userContext = usePermissionUserContext(); const handleClose = () => { setOpen(false); @@ -118,250 +103,7 @@ export const PermissionShield: React.FC = ({ return <>{children}; } - const tooltipTitle = ( - - {/* Title: AUTHORIZATION REQUIRED — medium gray */} - - Authorization Required - - - {/* Subtitle */} - - Missing requisite key - - - {/* Divider */} - - - {/* Key Row: KeyIcon (doubles as copy button) + key name */} - - - { - e.stopPropagation(); - navigator.clipboard.writeText(permissionKey.id || ''); - setCopied(true); - setTimeout(() => setCopied(false), 1500); - }} - sx={{ - display: 'inline-flex', - cursor: 'pointer', - color: copied ? '#EBC024' : 'rgba(255, 255, 255, 0.7)', - transition: 'color 0.2s ease', - '&:hover': { - color: '#EBC024' - } - }} - > - - - - - {permissionKey.function || 'Access Restricted'} - - - - {/* Description — italicized, equal padding both sides, no divider from key name */} - - - {permissionKey.description || - `Allows you to perform the ${permissionKey.function || 'selected'} operation.`} - - - - {/* Divider */} - - - {/* Bottom row: Category/Subcategory chips (left) + Key Reference link (right) */} - - - {permissionKey.category && ( - - )} - {permissionKey.subcategory && ( - - )} - - e.stopPropagation()} - sx={{ - display: 'inline-flex', - alignItems: 'baseline', - fontSize: '0.75rem', - color: '#EBC024', - textDecoration: 'none', - fontWeight: 600, - '&:hover': { - textDecoration: 'underline' - } - }} - > - Key Reference - - - - - - - {/* User / Org / Role context — provided via PermissionProvider */} - {userContext && (userContext.userName || userContext.orgName) && ( - <> - - - {userContext.userName && ( - - - - - User - - - - {userContext.userName} - - - )} - - - - - Org - - - - {userContext.orgName || 'Private Org'} - - - - - - - Role(s) - - - - {userContext.roleNames && userContext.roleNames.length > 0 - ? userContext.roleNames.join(', ') - : 'None'} - - - - - Seeing this message in error? Contact your Admins to request access. - - - )} - - ); + const tooltipTitle = ; const isBadge = variant === 'badge'; @@ -388,16 +130,7 @@ export const PermissionShield: React.FC = ({ disableTouchListener slotProps={{ tooltip: { - sx: { - background: '#1A1A1A', - color: '#FFFFFF', - maxWidth: 360, - minWidth: 300, - padding: '12px', - borderLeft: '4px solid #EBC024', - borderRadius: '8px', - boxShadow: '0 8px 32px rgba(0,0,0,0.4)' - } + sx: PERMISSION_SHIELD_CARD_SX } }} > diff --git a/src/index.tsx b/src/index.tsx index a1fb32597..53f2911a6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -72,8 +72,10 @@ export { } from './custom/permissions'; export { + PERMISSION_SHIELD_CARD_SX, PermissionProvider, PermissionShield, + PermissionShieldContent, useHasPermission, usePermission, usePermissionUserContext, @@ -81,6 +83,7 @@ export { type PermissionAction, type PermissionProviderProps, type PermissionProviderValue, + type PermissionShieldContentProps, type PermissionShieldProps, type PermissionUserContext } from './custom/permissions';