This commit is contained in:
2025-09-01 17:17:27 -04:00
parent aa524d8ac7
commit 74b25eba27
33 changed files with 669 additions and 686 deletions

2
web-components/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist
node_modules

View File

@ -1,43 +0,0 @@
import React from 'react';
import { TablerIconsProps } from '@tabler/icons-react';
export interface ActionMenuItem {
key: string;
label: string;
icon?: React.ComponentType<TablerIconsProps>;
color?: string;
disabled?: boolean;
hidden?: boolean;
onClick: (item?: any) => void | Promise<void>;
confirm?: {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
};
show?: (item: any) => boolean;
}
export interface ActionMenuProps {
item?: any;
actions: ActionMenuItem[];
trigger?: 'dots' | 'button' | 'custom';
triggerLabel?: string;
triggerIcon?: React.ComponentType<TablerIconsProps>;
triggerProps?: any;
customTrigger?: React.ReactNode;
position?: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start';
withArrow?: boolean;
withinPortal?: boolean;
'aria-label'?: string;
}
declare const ActionMenu: React.FC<ActionMenuProps>;
export default ActionMenu;
export declare const createViewAction: (onView: (item: any) => void) => ActionMenuItem;
export declare const createEditAction: (onEdit: (item: any) => void) => ActionMenuItem;
export declare const createCopyAction: (onCopy: (item: any) => void) => ActionMenuItem;
export declare const createDeleteAction: (onDelete: (item: any) => void | Promise<void>, itemName?: string) => ActionMenuItem;
export declare const createArchiveAction: (onArchive: (item: any) => void) => ActionMenuItem;
export declare const createRestoreAction: (onRestore: (item: any) => void) => ActionMenuItem;
export declare const getUserActions: (onEdit: (item: any) => void, onDelete: (item: any) => void, onViewDetails?: (item: any) => void) => ActionMenuItem[];
export declare const getApplicationActions: (onEdit: (item: any) => void, onDelete: (item: any) => void, onConfigure?: (item: any) => void) => ActionMenuItem[];
export declare const getFunctionActions: (onEdit: (item: any) => void, onDelete: (item: any) => void, onExecute?: (item: any) => void, onViewLogs?: (item: any) => void) => ActionMenuItem[];
export declare const getTokenActions: (onRevoke: (item: any) => void, onCopy?: (item: any) => void, onRefresh?: (item: any) => void) => ActionMenuItem[];

View File

@ -1,45 +0,0 @@
import React from 'react';
import { ListItem, FilterOptions } from '../../types';
export interface TableColumn {
key: string;
label: string;
sortable?: boolean;
filterable?: boolean;
width?: string | number;
render?: (value: any, item: ListItem) => React.ReactNode;
}
export interface TableAction {
key: string;
label: string;
icon?: React.ReactNode;
color?: string;
onClick: (item: ListItem) => void;
show?: (item: ListItem) => boolean;
}
export interface DataTableProps {
data: ListItem[];
columns: TableColumn[];
loading?: boolean;
error?: string | null;
title?: string;
total?: number;
page?: number;
pageSize?: number;
onPageChange?: (page: number) => void;
onAdd?: () => void;
onEdit?: (item: ListItem) => void;
onDelete?: (item: ListItem) => Promise<void>;
onRefresh?: () => void;
customActions?: TableAction[];
searchable?: boolean;
filterable?: boolean;
filters?: FilterOptions;
onFiltersChange?: (filters: FilterOptions) => void;
withBorder?: boolean;
withColumnBorders?: boolean;
striped?: boolean;
highlightOnHover?: boolean;
emptyMessage?: string;
}
declare const DataTable: React.FC<DataTableProps>;
export default DataTable;

View File

@ -1,47 +0,0 @@
import React from 'react';
import { TablerIconsProps } from '@tabler/icons-react';
export type EmptyStateVariant = 'no-data' | 'no-results' | 'error' | 'loading-failed' | 'access-denied' | 'coming-soon';
export type EmptyStateContext = 'users' | 'applications' | 'functions' | 'tokens' | 'executions' | 'permissions' | 'audit' | 'generic';
export interface EmptyStateAction {
label: string;
onClick: () => void;
variant?: 'filled' | 'light' | 'outline';
color?: string;
leftSection?: React.ReactNode;
}
export interface EmptyStateProps {
variant?: EmptyStateVariant;
context?: EmptyStateContext;
title?: string;
message?: string;
icon?: React.ComponentType<TablerIconsProps>;
iconSize?: number;
iconColor?: string;
actions?: EmptyStateAction[];
height?: number | string;
}
declare const EmptyState: React.FC<EmptyStateProps & {
onAdd?: () => void;
onRefresh?: () => void;
onClearFilters?: () => void;
}>;
export default EmptyState;
export declare const NoUsersState: React.FC<Omit<EmptyStateProps, 'context' | 'variant'> & {
onAddUser?: () => void;
}>;
export declare const NoApplicationsState: React.FC<Omit<EmptyStateProps, 'context' | 'variant'> & {
onCreateApp?: () => void;
}>;
export declare const NoFunctionsState: React.FC<Omit<EmptyStateProps, 'context' | 'variant'> & {
onCreateFunction?: () => void;
}>;
export declare const NoTokensState: React.FC<Omit<EmptyStateProps, 'context' | 'variant'> & {
onGenerateToken?: () => void;
}>;
export declare const NoSearchResults: React.FC<Omit<EmptyStateProps, 'variant'> & {
onClearFilters?: () => void;
onRefresh?: () => void;
}>;
export declare const ErrorState: React.FC<Omit<EmptyStateProps, 'variant'> & {
onRetry?: () => void;
}>;

View File

@ -1,17 +0,0 @@
import React from 'react';
import { FormField } from '../../types';
export interface FormSidebarProps {
opened: boolean;
onClose: () => void;
onSuccess: () => void;
title: string;
editMode?: boolean;
editItem?: any;
fields: FormField[];
onSubmit: (values: any) => Promise<void>;
width?: number;
initialValues?: Record<string, any>;
validateOnSubmit?: boolean;
}
declare const FormSidebar: React.FC<FormSidebarProps>;
export default FormSidebar;

View File

@ -1,46 +0,0 @@
import React from 'react';
export type LoadingVariant = 'spinner' | 'progress' | 'skeleton-table' | 'skeleton-cards' | 'skeleton-form' | 'skeleton-text' | 'dots' | 'overlay';
export type LoadingSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export interface LoadingStateProps {
variant?: LoadingVariant;
size?: LoadingSize;
height?: number | string;
message?: string;
submessage?: string;
progress?: number;
progressLabel?: string;
rows?: number;
columns?: number;
color?: string;
withContainer?: boolean;
animate?: boolean;
}
declare const LoadingState: React.FC<LoadingStateProps>;
export default LoadingState;
export declare const TableLoadingState: React.FC<{
rows?: number;
columns?: number;
}>;
export declare const CardsLoadingState: React.FC<{
count?: number;
columns?: number;
}>;
export declare const FormLoadingState: React.FC<{
fields?: number;
}>;
export declare const PageLoadingState: React.FC<{
message?: string;
}>;
export declare const InlineLoadingState: React.FC<{
message?: string;
size?: LoadingSize;
}>;
export declare const useLoadingState: (initialLoading?: boolean) => {
loading: boolean;
progress: number;
startLoading: () => void;
stopLoading: () => void;
updateProgress: (value: number) => void;
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
setProgress: React.Dispatch<React.SetStateAction<number>>;
};

View File

@ -1,49 +0,0 @@
import React from 'react';
export interface SidebarProps {
opened: boolean;
onClose: () => void;
title: string;
width?: number;
position?: 'left' | 'right';
headerActions?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
zIndex?: number;
offsetTop?: number;
backgroundColor?: string;
borderColor?: string;
animationDuration?: string;
'aria-label'?: string;
}
declare const Sidebar: React.FC<SidebarProps>;
export default Sidebar;
export interface FormSidebarWrapperProps extends Omit<SidebarProps, 'children'> {
children: React.ReactNode;
cancelLabel?: string;
submitLabel?: string;
onCancel?: () => void;
onSubmit?: () => void;
submitDisabled?: boolean;
showFooterActions?: boolean;
}
export declare const FormSidebarWrapper: React.FC<FormSidebarWrapperProps>;
export interface DetailsSidebarProps extends Omit<SidebarProps, 'title'> {
itemName: string;
itemType?: string;
editButton?: React.ReactNode;
deleteButton?: React.ReactNode;
status?: React.ReactNode;
}
export declare const DetailsSidebar: React.FC<DetailsSidebarProps>;
export interface QuickSidebarProps extends Omit<SidebarProps, 'children'> {
content: React.ReactNode;
actions?: React.ReactNode;
}
export declare const QuickSidebar: React.FC<QuickSidebarProps>;
export declare const useSidebar: (initialOpened?: boolean) => {
opened: boolean;
open: () => void;
close: () => void;
toggle: () => void;
setOpened: React.Dispatch<React.SetStateAction<boolean>>;
};

View File

@ -1,18 +0,0 @@
import React from 'react';
import { BadgeProps } from '@mantine/core';
export type StatusVariant = 'status' | 'role' | 'runtime' | 'type' | 'severity' | 'execution';
export interface StatusBadgeProps extends Omit<BadgeProps, 'color' | 'children'> {
value: string;
variant?: StatusVariant;
customColorMap?: Record<string, string>;
}
declare const COLOR_MAPS: Record<StatusVariant, Record<string, string>>;
declare const DEFAULT_COLORS: Record<StatusVariant, string>;
declare const StatusBadge: React.FC<StatusBadgeProps>;
export default StatusBadge;
export { COLOR_MAPS, DEFAULT_COLORS };
export declare const UserRoleBadge: React.FC<Omit<StatusBadgeProps, 'variant'>>;
export declare const ApplicationTypeBadge: React.FC<Omit<StatusBadgeProps, 'variant'>>;
export declare const RuntimeBadge: React.FC<Omit<StatusBadgeProps, 'variant'>>;
export declare const ExecutionStatusBadge: React.FC<Omit<StatusBadgeProps, 'variant'>>;
export declare const SeverityBadge: React.FC<Omit<StatusBadgeProps, 'variant'>>;

View File

@ -1,25 +0,0 @@
import { AxiosInstance } from 'axios';
import { FilterOptions } from '../types';
export interface ApiServiceConfig {
baseURL: string;
defaultHeaders?: Record<string, string>;
timeout?: number;
}
export interface UseApiServiceReturn<T> {
data: T[];
loading: boolean;
error: string | null;
total: number;
hasMore: boolean;
client: AxiosInstance;
getAll: (filters?: FilterOptions) => Promise<T[]>;
getById: (id: string) => Promise<T>;
create: (data: Partial<T>) => Promise<T>;
update: (id: string, data: Partial<T>) => Promise<T>;
delete: (id: string) => Promise<void>;
clearError: () => void;
refresh: () => Promise<void>;
}
export declare const useApiService: <T extends {
id: string;
}>(config: ApiServiceConfig, endpoint: string) => UseApiServiceReturn<T>;

View File

@ -1,16 +0,0 @@
import { FilterOptions, ListItem } from '../types';
export interface UseDataFilterOptions {
searchFields?: string[];
defaultFilters?: FilterOptions;
debounceMs?: number;
}
export interface UseDataFilterReturn {
filteredData: ListItem[];
filters: FilterOptions;
setFilter: (key: string, value: any) => void;
clearFilters: () => void;
resetFilters: () => void;
searchTerm: string;
setSearchTerm: (term: string) => void;
}
export declare const useDataFilter: (data: ListItem[], options?: UseDataFilterOptions) => UseDataFilterReturn;

View File

@ -1,18 +0,0 @@
export { default as FormSidebar } from './components/FormSidebar/FormSidebar';
export { default as DataTable } from './components/DataTable/DataTable';
export { default as StatusBadge, UserRoleBadge, ApplicationTypeBadge, RuntimeBadge, ExecutionStatusBadge, SeverityBadge } from './components/StatusBadge/StatusBadge';
export { default as EmptyState, NoUsersState, NoApplicationsState, NoFunctionsState, NoTokensState, NoSearchResults, ErrorState } from './components/EmptyState/EmptyState';
export { default as Sidebar, FormSidebarWrapper, DetailsSidebar, QuickSidebar, useSidebar } from './components/Sidebar/Sidebar';
export { default as ActionMenu, createViewAction, createEditAction, createCopyAction, createDeleteAction, createArchiveAction, createRestoreAction, getUserActions, getApplicationActions, getFunctionActions, getTokenActions } from './components/ActionMenu/ActionMenu';
export { default as LoadingState, TableLoadingState, CardsLoadingState, FormLoadingState, PageLoadingState, InlineLoadingState, useLoadingState } from './components/LoadingState/LoadingState';
export * from './types';
export { useApiService } from './hooks/useApiService';
export { useDataFilter } from './hooks/useDataFilter';
export * from './utils/notifications';
export * from './utils/validation';
export { Paper, Stack, Group, Button, TextInput, Select, MultiSelect, NumberInput, Textarea, JsonInput, ActionIcon, Menu, Text, Title, Badge, Table, Pagination, LoadingOverlay, Center, Box, ScrollArea, Divider, } from '@mantine/core';
export { useDisclosure, useToggle, useLocalStorage, } from '@mantine/hooks';
export { useForm } from '@mantine/form';
export { notifications } from '@mantine/notifications';
export { modals } from '@mantine/modals';
export { IconPlus, IconEdit, IconTrash, IconSearch, IconFilter, IconRefresh, IconX, IconDots, IconChevronDown, IconChevronRight, IconUser, IconUsers, IconKey, IconSettings, IconEye, IconEyeOff, IconCopy, IconCheck, IconAlertCircle, IconInfoCircle, } from '@tabler/icons-react';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,84 +0,0 @@
export interface BaseEntity {
id: string;
created_at: string;
updated_at: string;
created_by?: string;
updated_by?: string;
}
export interface Owner {
type: 'individual' | 'team';
name: string;
owner: string;
}
export interface FormSidebarProps {
opened: boolean;
onClose: () => void;
onSuccess: () => void;
editItem?: any;
}
export interface ListItem {
id: string;
name?: string;
title?: string;
email?: string;
status?: string;
role?: string;
type?: string;
[key: string]: any;
}
export interface ApiResponse<T> {
data: T;
message?: string;
error?: string;
}
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
has_more: boolean;
}
export interface FilterOptions {
search?: string;
status?: string;
type?: string;
role?: string;
limit?: number;
offset?: number;
[key: string]: any;
}
export interface NotificationConfig {
title: string;
message: string;
color: 'red' | 'green' | 'blue' | 'yellow' | 'gray';
[key: string]: any;
}
export interface ValidationRule {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
email?: boolean;
url?: boolean;
custom?: (value: any) => string | null;
}
export interface FormField {
name: string;
label: string;
type: 'text' | 'email' | 'number' | 'select' | 'multiselect' | 'textarea' | 'date' | 'json';
placeholder?: string;
description?: string;
required?: boolean;
disabled?: boolean;
options?: Array<{
value: string;
label: string;
}>;
validation?: ValidationRule;
defaultValue?: any;
}
export type { StatusVariant, StatusBadgeProps } from '../components/StatusBadge/StatusBadge';
export type { EmptyStateVariant, EmptyStateContext, EmptyStateProps, EmptyStateAction } from '../components/EmptyState/EmptyState';
export type { SidebarProps, FormSidebarWrapperProps, DetailsSidebarProps, QuickSidebarProps } from '../components/Sidebar/Sidebar';
export type { ActionMenuItem, ActionMenuProps } from '../components/ActionMenu/ActionMenu';
export type { LoadingVariant, LoadingSize, LoadingStateProps } from '../components/LoadingState/LoadingState';

View File

@ -1,37 +0,0 @@
export declare const showSuccessNotification: (message: string, title?: string) => void;
export declare const showErrorNotification: (message: string, title?: string) => void;
export declare const showWarningNotification: (message: string, title?: string) => void;
export declare const showInfoNotification: (message: string, title?: string) => void;
export declare const NotificationMessages: {
createSuccess: (entityName: string) => string;
updateSuccess: (entityName: string) => string;
deleteSuccess: (entityName: string) => string;
createError: (entityName: string) => string;
updateError: (entityName: string) => string;
deleteError: (entityName: string) => string;
loadError: (entityName: string) => string;
networkError: string;
validationError: string;
requiredFieldError: (fieldName: string) => string;
authRequired: string;
permissionDenied: string;
sessionExpired: string;
applicationCreated: string;
applicationUpdated: string;
applicationDeleted: string;
tokenCreated: string;
tokenRevoked: string;
userCreated: string;
userUpdated: string;
userDeleted: string;
functionCreated: string;
functionUpdated: string;
functionDeleted: string;
executionStarted: string;
executionCompleted: string;
executionFailed: string;
};
export declare const showCrudNotification: {
success: (operation: "create" | "update" | "delete", entityName: string) => void;
error: (operation: "create" | "update" | "delete" | "load", entityName: string, customMessage?: string) => void;
};

View File

@ -1,37 +0,0 @@
export declare const ValidationPatterns: {
email: RegExp;
url: RegExp;
duration: RegExp;
token: RegExp;
appId: RegExp;
uuid: RegExp;
};
export declare const ValidationMessages: {
required: (fieldName: string) => string;
email: string;
url: string;
duration: string;
minLength: (fieldName: string, minLength: number) => string;
maxLength: (fieldName: string, maxLength: number) => string;
pattern: (fieldName: string) => string;
token: string;
appId: string;
uuid: string;
positiveNumber: string;
range: (fieldName: string, min: number, max: number) => string;
};
export declare const validateRequired: (value: any) => string | null;
export declare const validateEmail: (value: string) => string | null;
export declare const validateUrl: (value: string) => string | null;
export declare const validateDuration: (value: string) => string | null;
export declare const validateMinLength: (value: string, minLength: number, fieldName?: string) => string | null;
export declare const validateMaxLength: (value: string, maxLength: number, fieldName?: string) => string | null;
export declare const validatePattern: (value: string, pattern: RegExp, fieldName?: string) => string | null;
export declare const validateRange: (value: number, min: number, max: number, fieldName?: string) => string | null;
export declare const validateAppId: (value: string) => string | null;
export declare const validateToken: (value: string) => string | null;
export declare const validateUuid: (value: string) => string | null;
export declare const validateJsonString: (value: string) => string | null;
export declare const parseDuration: (duration: string) => number;
export declare const formatDuration: (seconds: number) => string;
export declare const combineValidators: (...validators: Array<(value: any) => string | null>) => (value: any) => string | null;

View File

@ -20,6 +20,9 @@ export interface SidebarProps {
footer?: React.ReactNode;
children: React.ReactNode;
// Layout mode - when true, sidebar fills container instead of fixed positioning
layoutMode?: boolean;
// Styling customization
zIndex?: number;
offsetTop?: number;
@ -42,6 +45,7 @@ const Sidebar: React.FC<SidebarProps> = ({
headerActions,
footer,
children,
layoutMode = false,
zIndex = 1000,
offsetTop = 60,
backgroundColor = 'var(--mantine-color-body)',
@ -49,30 +53,46 @@ const Sidebar: React.FC<SidebarProps> = ({
animationDuration = '0.3s',
'aria-label': ariaLabel,
}) => {
// Calculate position styles based on position prop
// Calculate position styles based on layout mode
const getPositionStyles = () => {
const baseStyles = {
position: 'fixed' as const,
top: offsetTop,
bottom: 0,
width: `${width}px`,
zIndex,
borderRadius: 0,
display: 'flex',
flexDirection: 'column' as const,
backgroundColor,
height: '100%',
};
if (layoutMode) {
// In layout mode, sidebar fills its container (managed by SidebarLayout)
return {
...baseStyles,
position: 'relative' as const,
borderLeft: position === 'right' ? `1px solid ${borderColor}` : undefined,
borderRight: position === 'left' ? `1px solid ${borderColor}` : undefined,
};
}
// Legacy fixed positioning mode (for backward compatibility)
const fixedStyles = {
...baseStyles,
position: 'fixed' as const,
top: offsetTop,
bottom: 0,
zIndex,
transition: `${position} ${animationDuration} ease`,
};
if (position === 'right') {
return {
...baseStyles,
...fixedStyles,
right: opened ? 0 : `-${width}px`,
borderLeft: `1px solid ${borderColor}`,
};
} else {
return {
...baseStyles,
...fixedStyles,
left: opened ? 0 : `-${width}px`,
borderRight: `1px solid ${borderColor}`,
};

View File

@ -0,0 +1,170 @@
import React from 'react';
import { Box } from '@mantine/core';
export interface SidebarLayoutProps {
children: React.ReactNode;
sidebar?: React.ReactNode;
sidebarOpened?: boolean;
sidebarWidth?: number;
sidebarPosition?: 'left' | 'right';
offsetTop?: number;
className?: string;
// Animation settings
transitionDuration?: string;
}
/**
* SidebarLayout provides a responsive layout that shrinks the main content area
* when a sidebar is opened, rather than overlaying on top of the content.
*
* This ensures the main content remains visible and accessible when sidebars are open.
*/
const SidebarLayout: React.FC<SidebarLayoutProps> = ({
children,
sidebar,
sidebarOpened = false,
sidebarWidth = 450,
sidebarPosition = 'right',
offsetTop = 60,
transitionDuration = '0.3s',
className,
}) => {
// Calculate main content area margins based on sidebar state
const getMainContentStyles = (): React.CSSProperties => {
if (!sidebarOpened) {
return {
marginLeft: 0,
marginRight: 0,
transition: `margin ${transitionDuration} ease`,
};
}
return {
marginLeft: sidebarPosition === 'left' ? `${sidebarWidth}px` : 0,
marginRight: sidebarPosition === 'right' ? `${sidebarWidth}px` : 0,
transition: `margin ${transitionDuration} ease`,
};
};
// Calculate sidebar container styles for proper positioning
const getSidebarContainerStyles = (): React.CSSProperties => ({
position: 'fixed',
top: offsetTop,
bottom: 0,
width: `${sidebarWidth}px`,
zIndex: 1000,
[sidebarPosition]: sidebarOpened ? 0 : `-${sidebarWidth}px`,
transition: `${sidebarPosition} ${transitionDuration} ease`,
pointerEvents: sidebarOpened ? 'auto' : 'none',
});
return (
<Box className={className} style={{ position: 'relative', minHeight: '100%' }}>
{/* Main Content Area - adjusts width based on sidebar state */}
<Box style={getMainContentStyles()}>
{children}
</Box>
{/* Sidebar Container - positioned absolutely but doesn't overlay content */}
{sidebar && (
<Box style={getSidebarContainerStyles()}>
{sidebar}
</Box>
)}
</Box>
);
};
export default SidebarLayout;
/**
* Higher-level wrapper that combines SidebarLayout with responsive behavior
* and mobile-friendly overlays when screen size is too small.
*/
export interface ResponsiveSidebarLayoutProps extends SidebarLayoutProps {
mobileBreakpoint?: number;
overlayOnMobile?: boolean;
}
export const ResponsiveSidebarLayout: React.FC<ResponsiveSidebarLayoutProps> = ({
mobileBreakpoint = 768,
overlayOnMobile = true,
...props
}) => {
const [isMobile, setIsMobile] = React.useState(false);
React.useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < mobileBreakpoint);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, [mobileBreakpoint]);
// On mobile, use overlay behavior instead of shrinking content
if (isMobile && overlayOnMobile) {
return (
<Box style={{ position: 'relative', minHeight: '100%' }}>
{props.children}
{props.sidebar && props.sidebarOpened && (
<Box
style={{
position: 'fixed',
top: props.offsetTop || 60,
bottom: 0,
[props.sidebarPosition || 'right']: 0,
width: `${props.sidebarWidth || 450}px`,
zIndex: 1000,
transition: `transform ${props.transitionDuration || '0.3s'} ease`,
}}
>
{props.sidebar}
</Box>
)}
{/* Mobile overlay backdrop */}
{props.sidebarOpened && (
<Box
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: 999,
}}
onClick={() => {
// If sidebar has onClose, call it
if (React.isValidElement(props.sidebar) && props.sidebar.props.onClose) {
props.sidebar.props.onClose();
}
}}
/>
)}
</Box>
);
}
return <SidebarLayout {...props} />;
};
// Hook for managing sidebar layout state
export const useSidebarLayout = (initialOpened = false) => {
const [sidebarOpened, setSidebarOpened] = React.useState(initialOpened);
const openSidebar = React.useCallback(() => setSidebarOpened(true), []);
const closeSidebar = React.useCallback(() => setSidebarOpened(false), []);
const toggleSidebar = React.useCallback(() => setSidebarOpened(prev => !prev), []);
return {
sidebarOpened,
openSidebar,
closeSidebar,
toggleSidebar,
setSidebarOpened,
};
};

View File

@ -4,6 +4,7 @@ export { default as DataTable } from './components/DataTable/DataTable';
export { default as StatusBadge, UserRoleBadge, ApplicationTypeBadge, RuntimeBadge, ExecutionStatusBadge, SeverityBadge } from './components/StatusBadge/StatusBadge';
export { default as EmptyState, NoUsersState, NoApplicationsState, NoFunctionsState, NoTokensState, NoSearchResults, ErrorState } from './components/EmptyState/EmptyState';
export { default as Sidebar, FormSidebarWrapper, DetailsSidebar, QuickSidebar, useSidebar } from './components/Sidebar/Sidebar';
export { default as SidebarLayout, ResponsiveSidebarLayout, useSidebarLayout } from './components/SidebarLayout/SidebarLayout';
export { default as ActionMenu, createViewAction, createEditAction, createCopyAction, createDeleteAction, createArchiveAction, createRestoreAction, getUserActions, getApplicationActions, getFunctionActions, getTokenActions } from './components/ActionMenu/ActionMenu';
export { default as LoadingState, TableLoadingState, CardsLoadingState, FormLoadingState, PageLoadingState, InlineLoadingState, useLoadingState } from './components/LoadingState/LoadingState';