Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions src/collections/sistent/components/permission/code.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: Permission Code
component: permission
description: Integration details, code examples, and API references for the PermissionShield component.
---

import { PermissionShield, Button, PermissionSessionContext } from "@sistent/sistent";

The `PermissionShield` component acts as a high-level wrapper to restrict and decorate interactive components.

> **Important Security Note:** UI gating is a presentation-layer control only. Client-side checks must not decide access. The backend server and API routes must always enforce authorization on every request.

<a id="installation">
<h2>Installation</h2>
</a>

```bash
npm install @sistent/sistent
```

<a id="basic-usage">
<h2>Basic Usage</h2>
</a>

Wrap any standard component (e.g., a `Button`) inside the `PermissionShield` to automatically overlay the lock badge when permissions are restricted.

<div className="showcase">
<div className="items">
<ThemeWrapper>
<PermissionShield
permissionKey={{
id: "create_design",
category: "Catalog Management",
subcategory: "Designs",
function: "Create new design",
description: "Create new Meshery design"
}}
>
<Button variant="contained" color="primary">
Create New Design
</Button>
</PermissionShield>
</ThemeWrapper>
</div>
<CodeBlock name="basic-permission-shield" collapsible code={`import { PermissionShield, Button } from "@sistent/sistent";

export const CreateDesignButton = () => (
<PermissionShield
permissionKey={{
id: "create_design",
category: "Catalog Management",
subcategory: "Designs",
function: "Create new design",
description: "Create new Meshery design"
}}
>
<Button variant="contained" color="primary">
Create New Design
</Button>
</PermissionShield>
);`} />
</div>

<a id="custom-boundary-padding">
<h2>Custom Boundary Padding</h2>
</a>

Use the `boundaryPadding` prop to configure custom offsets if the tooltip overlaps fixed elements (like headers, sidebars, or navbar sections) during scrolling.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think boundaryPadding does not exist in the codebase. Can you check again in the layer5io/sistent repo?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The boundaryPadding prop is currently on the fixing-tools branch of the sistent repository introduced in the tooltip responsiveness fixes and will be available once that branch is merged into master


<div className="showcase">
<div className="items">
<ThemeWrapper>
<PermissionShield
permissionKey={{
id: "view_connections",
category: "Workspace Management",
subcategory: "Connections",
function: "View Connections",
description: "View all connections within an environment"
}}
boundaryPadding={{ top: 85, left: 270 }}
>
<Button variant="outlined">
Connections
</Button>
</PermissionShield>
</ThemeWrapper>
</div>
<CodeBlock name="custom-boundary-padding" collapsible code={`import { PermissionShield, Button } from "@sistent/sistent";

export const GatedConnectionsButton = () => (
<PermissionShield
permissionKey={{
id: "view_connections",
category: "Workspace Management",
subcategory: "Connections",
function: "View Connections",
description: "View all connections within an environment"
}}
boundaryPadding={{ top: 85, left: 270 }}
>
<Button variant="outlined">
Connections
</Button>
</PermissionShield>
);`} />
</div>

<a id="permission-session-context-usage">
<h2>Permission Session Context</h2>
</a>

Use `PermissionSessionContext` to embed permission details directly inside custom components or full-page 403 authorization layouts without utilizing tooltips.

<div className="showcase">
<div className="items">
<ThemeWrapper>
<div style={{ width: "100%", maxWidth: "450px" }}>
<PermissionSessionContext
variant="card"
permissionKey={{
id: "delete_connection",
category: "Lifecycle Management",
subcategory: "Connections",
function: "Delete a connection",
description: "Delete a connection"
}}
/>
</div>
</ThemeWrapper>
</div>
<CodeBlock name="permission-session-context-card" collapsible code={`import { PermissionSessionContext } from "@sistent/sistent";

export const ForbiddenPage = () => (
<div style={{ padding: "24px" }}>
<h1>Access Denied</h1>
<PermissionSessionContext
variant="card"
permissionKey={{
id: "delete_connection",
category: "Lifecycle Management",
subcategory: "Connections",
function: "Delete a connection",
description: "Delete a connection"
}}
/>
</div>
);`} />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>

<a id="api-reference">
<h2>API Reference</h2>
</a>

<h3>PermissionShield Props</h3>

| Name | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `permissionKey` | `PermissionKeySpec` | - | The unique key specification that the gated action requires. |
| `children` | `React.ReactNode` | - | The gated component (e.g., Button, IconButton) to be wrapped. |
| `variant` | `'inline' \| 'badge'` | `'inline'` | The display format of the lock badge. `'inline'` renders the lock icon at the center-right (end adornment), `'badge'` renders it overlaying the bottom-left corner. |
| `boundaryPadding` | `object` | `{ top: 85, left: 16, right: 8, bottom: 8 }` | Optional offsets to prevent tooltips from overlapping headers/menus. The default is responsive: `{ top: 64, left: 8, right: 8, bottom: 8 }` on mobile and `{ top: 85, left: 16, right: 8, bottom: 8 }` on desktop. |

<h3>PermissionSessionContext Props</h3>

| Name | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `variant` | `'tooltip' \| 'card'` | `'tooltip'` | The layout style for the session context. `'tooltip'` uses compact dark-themed styling, while `'card'` provides theme-aware, scaled-up styling for full-page or card display. |
| `permissionKey` | `PermissionKeySpec` | - | Optional. When supplied, the component resolves the unmet keys, subcategories, and description automatically. |
| `displayedKeys` | `Key[]` | - | Optional. Pre-resolved array of permission keys to display details for when manual key resolution is preferred. |
| `subtitle` | `string` | - | Optional. Pre-resolved subtitle (e.g. "Needs any of: ...") to display below the title. |
| `categories` | `string[]` | - | Optional. Pre-resolved category labels to display as chips. |
| `subcategories` | `string[]` | - | Optional. Pre-resolved subcategory labels to display as chips. |
65 changes: 65 additions & 0 deletions src/collections/sistent/components/permission/guidance.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Permission Guidance
component: permission
description: Guidance on using the Sistent Permission component and best practices for design system gating.
---

<a id="usage">
<h2>Usage</h2>
</a>

The `PermissionShield` component should be used to protect actions or affordances that require specific roles, permission keys, or capabilities.

> **Important Security Note:** UI gating is a presentation-layer control only. Client-side checks must not decide access. The backend server and API routes must always enforce authorization on every request.

<ul>
<li>Wrap gated interactive components (Buttons, IconButtons, Tabs) to provide visual feedback to users when they are restricted.</li>
<li>Ensure the locked state clearly informs the user which permissions are missing, reducing frustration and confusion.</li>
<li>Use responsive layouts or custom boundary paddings where the tooltips might get clipped by sidebars or sticky headers.</li>
</ul>

<a id="when-to-use">
<h2>When to Use</h2>
</a>

<h3>Disable versus Hide</h3>

When deciding whether to restrict a feature, follow these design system guidelines:

<ul>
<li>
<strong>Disable (Keep Visible with PermissionShield):</strong> Use this when the action is part of a standard page flow (like a "Delete Connection" button). Keeping it visible helps users understand that the feature exists, and the lock badge tells them exactly why they cannot click it and who to contact.
</li>
<li>
<strong>Hide (Remove from Layout):</strong> Use this when the user has absolutely no access to the entire page or section (e.g. an "Admin Console" menu link or a full tab panel that they do not have clearance to view).
</li>
</ul>

<h3>Tooltip versus Card Layouts</h3>

When displaying permission details using `PermissionSessionContext`, choose the variant based on the integration context:

<ul>
<li>
<strong>Tooltip Layout (default):</strong> Use this inside popovers, hover tooltips, and overlay badges (e.g., within `PermissionShield`) to display compact, dark-background metadata without displacing layout elements.
</li>
<li>
<strong>Card Layout:</strong> Use this when embedding permission details directly into the page layout, such as full-page 403 authorization error layouts, settings sidebars, or container panels.
</li>
</ul>

<a id="best-practices">
<h2>Best Practices</h2>
</a>

<ul>
<li>
<strong>Consistent Badge Placement:</strong> Use `'inline'` (default) to place the lock badge at the center-right of the component (common for text fields, buttons, and inline controls). Use the `'badge'` variant to overlay the badge at the bottom-left corner of the parent component when appropriate (e.g., custom cards, list items, or standalone elements).
</li>
<li>
<strong>Accurate Tooltips:</strong> Ensure the `permissionKey` prop is fully populated with accurate names so the tooltip can display the exact missing keys.
</li>
<li>
<strong>Nested Disabled Controls:</strong> Do not rely solely on the wrapper to block interactions. Set the nested control (e.g. Button) to `disabled` (or `aria-disabled` for custom controls) and handle `preventDefault()` where needed so that default actions, keyboard accessibility, and screen readers behave correctly.
</li>
</ul>
112 changes: 112 additions & 0 deletions src/collections/sistent/components/permission/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
name: "Permission"
title: "Permission"
published: true
component: permission
description: The PermissionShield component gates and protects interactive actions, buttons, and navigation elements. It displays a visual lock icon overlay or inline badge when a user lacks the necessary permission keys or capabilities.
---

import { PermissionShield, Button, PermissionSessionContext } from "@sistent/sistent";

The `PermissionShield` component provides a standard design pattern for feature-level permission gating across applications. Rather than silently hiding disabled elements, it keeps them visible (preserving layout continuity) and overlays a visual lock indicator to explain to users why the action is restricted.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

> **Important Security Note:** UI gating is a presentation-layer control only. Client-side checks must not decide access. The backend server and API routes must always enforce authorization on every request.

<a id="inline-shield-overlay">
<h2>Inline Shield Overlay</h2>
</a>

The default variant (`'inline'`) wraps any standard action component (like a `Button` or `IconButton`) and places a lock icon badge at the center-right of the target element (serving as an end adornment). When clicked, it shows a detailed explanation of the missing permission.

<Row className="image-container">
<ThemeWrapper>
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
<PermissionShield
permissionKey={{
id: "delete_connection",
category: "Lifecycle Management",
subcategory: "Connections",
function: "Delete a connection",
description: "Delete a connection"
}}
>
<Button variant="contained" color="error">
Delete Resource
</Button>
</PermissionShield>
</div>
</ThemeWrapper>
</Row>

<a id="badge-variant">
<h2>Badge Variant</h2>
</a>

The badge variant (`'badge'`) renders the permission badge overlaying the bottom-left corner of the target element. This is useful for list items, settings tables, or static text where placing the shield as an end-adornment on the right is not suitable.

<Row className="image-container">
<ThemeWrapper>
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
<PermissionShield
variant="badge"
permissionKey={{
id: "view_audit_logs",
category: "Events Management",
subcategory: "Audit",
function: "View Audit",
description: "View Audit Logs"
}}
>
<span>Audit Logs</span>
</PermissionShield>
</div>
</ThemeWrapper>
</Row>

<a id="permission-session-context">
<h2>Permission Session Context</h2>
</a>

The `PermissionSessionContext` component renders the detailed authorization metadata (user identity, organization, assigned roles, and missing keys) that is displayed inside tooltips or cards. It supports two layout variants:

- **Tooltip Variant (`'tooltip'`):** A compact layout designed to fit inside standard hover/click tooltips (used by default inside `PermissionShield`).
- **Card Variant (`'card'`):** A theme-aware, scaled-up layout designed to be embedded directly into custom cards, sidebars, or full-page 403/Forbidden error screens.

### Tooltip Layout

<Row className="image-container">
<ThemeWrapper>
<div style={{ padding: "16px", background: "#1A1A1A", borderRadius: "8px", maxWidth: "360px", width: "100%" }}>
<PermissionSessionContext
variant="tooltip"
permissionKey={{
id: "delete_connection",
category: "Lifecycle Management",
subcategory: "Connections",
function: "Delete a connection",
description: "Delete a connection"
}}
/>
</div>
</ThemeWrapper>
</Row>

### Card Layout

<Row className="image-container">
<ThemeWrapper>
<div style={{ width: "100%", maxWidth: "450px" }}>
<PermissionSessionContext
variant="card"
permissionKey={{
id: "delete_connection",
category: "Lifecycle Management",
subcategory: "Connections",
function: "Delete a connection",
description: "Delete a connection"
}}
/>
</div>
</ThemeWrapper>
</Row>