Socket
Book a DemoInstallSign in
Socket

@rytass/cms-react-components

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@rytass/cms-react-components

Rytass Content Management System React Components

0.2.1
latest
Source
npmnpm
Version published
Weekly downloads
14
16.67%
Maintainers
1
Weekly downloads
 
Created
Source

Rytass Utils - CMS React Components

A comprehensive collection of production-ready React components for building content management systems. Built with Mezzanine UI design system, these components provide standardized interfaces for article management, workflow controls, permissions, and rich content editing with complete TypeScript support.

Features

Core Components

  • StandardCMSTable - Advanced data table with sorting, filtering, and actions
  • StandardCMSFormActions - Form submission with workflow actions
  • StandardCMSList - List view with pagination and selection
  • StandardCMSTabs - Tab-based content organization
  • Textarea with rich text support

Modal Components

  • DeleteWithdrawModal - Article deletion/withdrawal confirmation
  • RejectModal - Article rejection with reasons
  • VerifyReleaseModal - Release verification workflow
  • LogsModal - Version history and audit logs

Context Providers

  • DialogProvider - Centralized dialog management
  • ModalProvider - Modal state management
  • Permission utilities for role-based access
  • Built-in form validation with react-hook-form

Article Workflow Management

  • Complete article lifecycle (Draft � Reviewing � Verified � Released)
  • Permission-based action controls
  • Multi-stage approval workflows
  • Version control integration

Installation

npm install @rytass/cms-react-components
# Peer dependencies
npm install @mezzanine-ui/core @mezzanine-ui/react @mezzanine-ui/icons react react-dom react-hook-form dayjs lodash
# or
yarn add @rytass/cms-react-components

Basic Setup

Provider Configuration

// App.tsx
import React from 'react';
import { CalendarConfigProvider } from '@mezzanine-ui/react';
import calendarMethodsDayjs from '@mezzanine-ui/core/calendarMethodsDayjs';
import { DialogProvider, ModalProvider } from '@rytass/cms-react-components';

function App() {
  return (
    <CalendarConfigProvider methods={calendarMethodsDayjs}>
      <DialogProvider>
        <ModalProvider>
          {/* Your app components */}
        </ModalProvider>
      </DialogProvider>
    </CalendarConfigProvider>
  );
}

export default App;

Component Usage

StandardCMSTable

A comprehensive data table component with built-in sorting, filtering, pagination, and row actions.

import React from 'react';
import { StandardCMSTable, ArticleStage } from '@rytass/cms-react-components';
import { TableColumn } from '@mezzanine-ui/core/table';

interface Article {
  id: string;
  title: string;
  author: string;
  stage: ArticleStage;
  publishedAt: Date | null;
  updatedAt: Date;
}

const columns: TableColumn<Article>[] = [
  {
    title: 'Title',
    dataIndex: 'title',
    width: 300,
    ellipsis: true,
  },
  {
    title: 'Author',
    dataIndex: 'author',
    width: 150,
  },
  {
    title: 'Stage',
    dataIndex: 'stage',
    width: 120,
    render: (stage: ArticleStage) => (
      <StatusBadge stage={stage} />
    ),
  },
  {
    title: 'Published',
    dataIndex: 'publishedAt',
    width: 150,
    render: (date: Date | null) => 
      date ? new Date(date).toLocaleDateString() : '-',
  },
];

function ArticleManagement() {
  const [data, setData] = useState<Article[]>([]);
  const [loading, setLoading] = useState(false);
  const [pagination, setPagination] = useState({
    current: 1,
    pageSize: 20,
    total: 0,
  });

  const handleTableChange = (params: any) => {
    // Handle pagination, sorting, filtering
    fetchArticles(params);
  };

  const handleAction = (action: string, record: Article) => {
    switch (action) {
      case 'edit':
        navigateToEdit(record.id);
        break;
      case 'delete':
        confirmDelete(record);
        break;
      case 'publish':
        publishArticle(record);
        break;
    }
  };

  return (
    <StandardCMSTable<Article>
      columns={columns}
      dataSource={data}
      loading={loading}
      pagination={pagination}
      onChange={handleTableChange}
      onAction={handleAction}
      rowKey="id"
      selectable
      actionColumn={{
        title: 'Actions',
        width: 150,
        actions: (record) => getActionsForStage(record.stage),
      }}
    />
  );
}

StandardCMSFormActions

Form submission component with integrated workflow actions and validation.

import React from 'react';
import { useForm } from 'react-hook-form';
import { 
  StandardCMSFormActions,
  ArticleStage 
} from '@rytass/cms-react-components';

interface ArticleFormData {
  title: string;
  content: string;
  categoryId: string;
  tags: string[];
  publishDate?: Date;
}

function ArticleForm({ article, onSubmit }: ArticleFormProps) {
  const form = useForm<ArticleFormData>({
    defaultValues: {
      title: article?.title || '',
      content: article?.content || '',
      categoryId: article?.categoryId || '',
      tags: article?.tags || [],
    },
  });

  const handleSave = async (data: ArticleFormData) => {
    await saveArticle(data);
    showSuccess('Article saved as draft');
  };

  const handleSubmitForReview = async (data: ArticleFormData) => {
    await submitForReview(data);
    showSuccess('Article submitted for review');
  };

  const handlePublish = async (data: ArticleFormData) => {
    await publishArticle(data);
    showSuccess('Article published successfully');
  };

  return (
    <form>
      {/* Form fields */}
      <input {...form.register('title', { required: true })} />
      <textarea {...form.register('content', { required: true })} />
      
      <StandardCMSFormActions
        form={form}
        stage={article?.stage || ArticleStage.DRAFT}
        permissions={userPermissions}
        onSave={handleSave}
        onSubmit={handleSubmitForReview}
        onPublish={handlePublish}
        disableActions={form.formState.isSubmitting}
        saveButtonText="Save Draft"
        submitButtonText="Submit for Review"
        publishButtonText="Publish Now"
        showScheduleOption
        onSchedule={(date) => schedulePublication(data, date)}
      />
    </form>
  );
}

Modal Components

DeleteWithdrawModal

import React, { useState } from 'react';
import { 
  DeleteWithdrawModal,
  useModal 
} from '@rytass/cms-react-components';

function ArticleActions({ article }: { article: Article }) {
  const { openModal, closeModal } = useModal();
  
  const handleDelete = () => {
    openModal({
      component: DeleteWithdrawModal,
      props: {
        title: 'Delete Article',
        message: `Are you sure you want to delete "${article.title}"?`,
        itemType: 'article',
        onConfirm: async () => {
          await deleteArticle(article.id);
          closeModal();
          refreshList();
        },
        onCancel: closeModal,
        severity: 'error',
      },
    });
  };

  const handleWithdraw = () => {
    openModal({
      component: DeleteWithdrawModal,
      props: {
        title: 'Withdraw Article',
        message: `Withdraw "${article.title}" from publication?`,
        itemType: 'article',
        requireReason: true,
        onConfirm: async (reason: string) => {
          await withdrawArticle(article.id, reason);
          closeModal();
          refreshList();
        },
        onCancel: closeModal,
        severity: 'warning',
      },
    });
  };

  return (
    <div>
      <button onClick={handleDelete}>Delete</button>
      <button onClick={handleWithdraw}>Withdraw</button>
    </div>
  );
}

RejectModal

import React from 'react';
import { RejectModal, useModal } from '@rytass/cms-react-components';

function ReviewActions({ article }: { article: Article }) {
  const { openModal, closeModal } = useModal();
  
  const handleReject = () => {
    openModal({
      component: RejectModal,
      props: {
        title: 'Reject Article',
        itemName: article.title,
        reasons: [
          'Content needs improvement',
          'Missing required information',
          'Does not meet quality standards',
          'Contains inappropriate content',
          'Other',
        ],
        requireComment: true,
        onConfirm: async (reason: string, comment: string) => {
          await rejectArticle(article.id, { reason, comment });
          closeModal();
          showSuccess('Article rejected');
        },
        onCancel: closeModal,
      },
    });
  };

  return <button onClick={handleReject}>Reject</button>;
}

VerifyReleaseModal

import React from 'react';
import { 
  VerifyReleaseModal,
  useModal 
} from '@rytass/cms-react-components';

function PublishActions({ article }: { article: Article }) {
  const { openModal, closeModal } = useModal();
  
  const handleVerifyAndRelease = () => {
    openModal({
      component: VerifyReleaseModal,
      props: {
        title: 'Verify and Release Article',
        article: {
          title: article.title,
          author: article.author,
          categories: article.categories,
          lastModified: article.updatedAt,
        },
        checklist: [
          'Content has been reviewed for accuracy',
          'All images have proper attribution',
          'SEO metadata is complete',
          'Links have been verified',
        ],
        requireAllChecks: true,
        showPreview: true,
        previewUrl: `/preview/${article.id}`,
        onConfirm: async (checkedItems: string[]) => {
          await verifyAndRelease(article.id, { checkedItems });
          closeModal();
          showSuccess('Article published successfully');
        },
        onCancel: closeModal,
      },
    });
  };

  return <button onClick={handleVerifyAndRelease}>Verify & Release</button>;
}

LogsModal

import React from 'react';
import { LogsModal, useModal, LogEntry } from '@rytass/cms-react-components';

function ArticleHistory({ article }: { article: Article }) {
  const { openModal, closeModal } = useModal();
  
  const handleViewLogs = async () => {
    const logs = await fetchArticleLogs(article.id);
    
    openModal({
      component: LogsModal,
      props: {
        title: `History: ${article.title}`,
        logs: logs.map((log): LogEntry => ({
          id: log.id,
          timestamp: log.createdAt,
          user: log.user.name,
          action: log.action,
          details: log.details,
          changes: log.changes,
          version: log.version,
        })),
        showDiff: true,
        onRevert: async (logId: string) => {
          await revertToVersion(article.id, logId);
          closeModal();
          refreshArticle();
        },
        onClose: closeModal,
      },
    });
  };

  return <button onClick={handleViewLogs}>View History</button>;
}

StandardCMSList

List view component for displaying content cards or items.

import React from 'react';
import { StandardCMSList } from '@rytass/cms-react-components';

interface ListItem {
  id: string;
  title: string;
  thumbnail: string;
  description: string;
  status: string;
}

function ContentList() {
  const [items, setItems] = useState<ListItem[]>([]);
  const [selectedIds, setSelectedIds] = useState<string[]>([]);

  const handleSelect = (ids: string[]) => {
    setSelectedIds(ids);
  };

  const handleBulkAction = (action: string) => {
    switch (action) {
      case 'delete':
        bulkDelete(selectedIds);
        break;
      case 'archive':
        bulkArchive(selectedIds);
        break;
    }
  };

  return (
    <StandardCMSList<ListItem>
      items={items}
      selectedIds={selectedIds}
      onSelect={handleSelect}
      renderItem={(item) => (
        <ContentCard
          key={item.id}
          title={item.title}
          thumbnail={item.thumbnail}
          description={item.description}
          status={item.status}
          onClick={() => navigateToDetail(item.id)}
        />
      )}
      bulkActions={[
        { label: 'Delete', value: 'delete', severity: 'error' },
        { label: 'Archive', value: 'archive' },
      ]}
      onBulkAction={handleBulkAction}
      emptyMessage="No content found"
      loading={loading}
    />
  );
}

StandardCMSTabs

Tab navigation component for organizing content sections.

import React, { useState } from 'react';
import { StandardCMSTabs } from '@rytass/cms-react-components';

function ArticleEditor() {
  const [activeTab, setActiveTab] = useState('content');

  const tabs = [
    {
      key: 'content',
      label: 'Content',
      badge: hasUnsavedChanges ? '"' : undefined,
    },
    {
      key: 'metadata',
      label: 'Metadata',
    },
    {
      key: 'seo',
      label: 'SEO',
      disabled: !article.published,
    },
    {
      key: 'history',
      label: 'History',
      badge: versionCount,
    },
  ];

  return (
    <StandardCMSTabs
      tabs={tabs}
      activeTab={activeTab}
      onChange={setActiveTab}
      variant="contained"
      fullWidth
    >
      {activeTab === 'content' && <ContentEditor />}
      {activeTab === 'metadata' && <MetadataForm />}
      {activeTab === 'seo' && <SEOSettings />}
      {activeTab === 'history' && <VersionHistory />}
    </StandardCMSTabs>
  );
}

Permission Management

Using Permission Utilities

import { 
  havePermission,
  ArticlesPermissions 
} from '@rytass/cms-react-components';

function ArticleActions({ article, userPermissions }: Props) {
  const canEdit = havePermission(
    userPermissions,
    ArticlesPermissions.UpdateArticleInDraft
  );

  const canDelete = havePermission(
    userPermissions,
    ArticlesPermissions.DeleteArticleInDraft
  );

  const canSubmit = havePermission(
    userPermissions,
    ArticlesPermissions.SubmitPutBackArticle
  );

  const canApprove = havePermission(
    userPermissions,
    ArticlesPermissions.ApproveRejectArticle
  );

  return (
    <div>
      {canEdit && <button onClick={handleEdit}>Edit</button>}
      {canDelete && <button onClick={handleDelete}>Delete</button>}
      {canSubmit && <button onClick={handleSubmit}>Submit</button>}
      {canApprove && <button onClick={handleApprove}>Approve</button>}
    </div>
  );
}

Permission-Based Table Actions

import { 
  ArticleStage,
  ArticleTableActions,
  ArticleTableActionsType 
} from '@rytass/cms-react-components';

const getTableActions = (
  permissions: string[]
): ArticleTableActionsType => {
  const actions: ArticleTableActionsType = {};

  // Draft stage actions
  actions[ArticleStage.DRAFT] = [];
  if (havePermission(permissions, ArticlesPermissions.UpdateArticleInDraft)) {
    actions[ArticleStage.DRAFT].push(ArticleTableActions.Update);
  }
  if (havePermission(permissions, ArticlesPermissions.SubmitPutBackArticle)) {
    actions[ArticleStage.DRAFT].push(ArticleTableActions.Submit);
  }

  // Reviewing stage actions
  actions[ArticleStage.REVIEWING] = [];
  if (havePermission(permissions, ArticlesPermissions.ApproveRejectArticle)) {
    actions[ArticleStage.REVIEWING].push(ArticleTableActions.Review);
  }
  if (havePermission(permissions, ArticlesPermissions.SubmitPutBackArticle)) {
    actions[ArticleStage.REVIEWING].push(ArticleTableActions.PutBack);
  }

  // Released stage actions
  actions[ArticleStage.RELEASED] = [];
  if (havePermission(permissions, ArticlesPermissions.UpdateArticleInReleased)) {
    actions[ArticleStage.RELEASED].push(ArticleTableActions.Update);
  }
  if (havePermission(permissions, ArticlesPermissions.WithdrawArticleInReleased)) {
    actions[ArticleStage.RELEASED].push(ArticleTableActions.Withdraw);
  }

  return actions;
};

Article Workflow States

The CMS components support a complete article lifecycle with the following stages:

import { ArticleStage } from '@rytass/cms-react-components';

// Article lifecycle stages
const stages = {
  [ArticleStage.DRAFT]: 'Draft - Initial creation state',
  [ArticleStage.REVIEWING]: 'Under Review - Pending approval',
  [ArticleStage.VERIFIED]: 'Verified - Approved but not published',
  [ArticleStage.SCHEDULED]: 'Scheduled - Set for future publication',
  [ArticleStage.RELEASED]: 'Published - Live and public',
  [ArticleStage.UNKNOWN]: 'Unknown - Error or undefined state',
};

// Stage transitions
function getNextStage(currentStage: ArticleStage): ArticleStage | null {
  switch (currentStage) {
    case ArticleStage.DRAFT:
      return ArticleStage.REVIEWING;
    case ArticleStage.REVIEWING:
      return ArticleStage.VERIFIED;
    case ArticleStage.VERIFIED:
      return ArticleStage.RELEASED;
    default:
      return null;
  }
}

Custom Hooks

useDialog Hook

import { useDialog } from '@rytass/cms-react-components';

function MyComponent() {
  const { openDialog, closeDialog } = useDialog();

  const handleConfirmAction = () => {
    openDialog({
      title: 'Confirm Action',
      children: 'Are you sure you want to proceed?',
      severity: 'warning',
      confirmText: 'Proceed',
      cancelText: 'Cancel',
      resolve: (confirmed: boolean) => {
        if (confirmed) {
          performAction();
        }
        closeDialog();
      },
    });
  };

  return <button onClick={handleConfirmAction}>Perform Action</button>;
}

useModal Hook

import { useModal } from '@rytass/cms-react-components';

function MyComponent() {
  const { openModal, closeModal, isOpen } = useModal();

  const handleOpenCustomModal = () => {
    openModal({
      component: CustomModal,
      props: {
        title: 'Custom Modal',
        data: someData,
        onSave: (result) => {
          handleSave(result);
          closeModal();
        },
        onCancel: closeModal,
      },
    });
  };

  return (
    <div>
      <button onClick={handleOpenCustomModal}>Open Modal</button>
      {isOpen && <div>Modal is open</div>}
    </div>
  );
}

Styling and Theming

Using Mezzanine UI Theme

import { MezzanineProvider, createTheme } from '@mezzanine-ui/react';
import { StandardCMSTable } from '@rytass/cms-react-components';

const customTheme = createTheme({
  palette: {
    primary: '#1976d2',
    secondary: '#dc004e',
  },
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
  },
});

function ThemedApp() {
  return (
    <MezzanineProvider theme={customTheme}>
      <StandardCMSTable {...tableProps} />
    </MezzanineProvider>
  );
}

Custom Styling

// custom-cms-styles.scss
@import '@mezzanine-ui/core/styles';

.custom-cms-table {
  .mzn-table {
    &__header {
      background-color: var(--mzn-color-surface-variant);
    }

    &__row {
      &:hover {
        background-color: var(--mzn-color-action-hover);
      }
    }
  }
}

.custom-form-actions {
  .mzn-button-group {
    gap: 8px;
    
    .mzn-button {
      min-width: 120px;
    }
  }
}

TypeScript Support

Type Definitions

import {
  StandardCMSTableProps,
  StandardCMSFormActionsProps,
  ArticleStage,
  ArticleTableActions,
  ArticlesPermissions,
  LogEntry,
  DialogConfigType,
  ModalConfigType,
} from '@rytass/cms-react-components';

// Custom article type
interface CustomArticle {
  id: string;
  title: string;
  content: string;
  stage: ArticleStage;
  author: {
    id: string;
    name: string;
    email: string;
  };
  categories: string[];
  tags: string[];
  metadata: Record<string, any>;
  createdAt: Date;
  updatedAt: Date;
  publishedAt: Date | null;
}

// Table props with custom type
const tableProps: StandardCMSTableProps<CustomArticle> = {
  columns: [...],
  dataSource: articles,
  loading: false,
  // ... other props
};

// Form props with validation
interface ArticleFormData {
  title: string;
  content: string;
  categoryId: string;
}

const formProps: StandardCMSFormActionsProps<ArticleFormData> = {
  form: useForm<ArticleFormData>(),
  stage: ArticleStage.DRAFT,
  permissions: userPermissions,
  // ... other props
};

Best Practices

Performance Optimization

import React, { useMemo, useCallback } from 'react';
import { StandardCMSTable } from '@rytass/cms-react-components';

function OptimizedTable() {
  // Memoize columns to prevent re-renders
  const columns = useMemo(() => [
    { title: 'Title', dataIndex: 'title', width: 300 },
    { title: 'Author', dataIndex: 'author', width: 150 },
    // ... more columns
  ], []);

  // Use callback for action handlers
  const handleAction = useCallback((action: string, record: any) => {
    // Handle action
  }, []);

  // Virtual scrolling for large datasets
  return (
    <StandardCMSTable
      columns={columns}
      dataSource={data}
      onAction={handleAction}
      virtual
      rowHeight={48}
      visibleRows={20}
    />
  );
}

Error Handling

import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { StandardCMSFormActions } from '@rytass/cms-react-components';

function FormWithErrorHandling() {
  const handleError = (error: Error) => {
    console.error('Form error:', error);
    showErrorNotification(error.message);
  };

  return (
    <ErrorBoundary
      fallback={<ErrorFallback />}
      onError={handleError}
    >
      <StandardCMSFormActions
        {...formProps}
        onError={handleError}
      />
    </ErrorBoundary>
  );
}

Accessibility

import { StandardCMSTable } from '@rytass/cms-react-components';

function AccessibleTable() {
  return (
    <StandardCMSTable
      {...tableProps}
      ariaLabel="Articles management table"
      ariaDescribedBy="table-description"
      rowAriaLabel={(record) => `Article: ${record.title}`}
      actionAriaLabels={{
        edit: 'Edit article',
        delete: 'Delete article',
        publish: 'Publish article',
      }}
    />
  );
}

Testing

Component Testing

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { StandardCMSTable } from '@rytass/cms-react-components';

describe('StandardCMSTable', () => {
  const mockData = [
    { id: '1', title: 'Article 1', stage: 'DRAFT' },
    { id: '2', title: 'Article 2', stage: 'PUBLISHED' },
  ];

  it('renders table with data', () => {
    render(
      <StandardCMSTable
        columns={columns}
        dataSource={mockData}
        rowKey="id"
      />
    );

    expect(screen.getByText('Article 1')).toBeInTheDocument();
    expect(screen.getByText('Article 2')).toBeInTheDocument();
  });

  it('handles row actions', async () => {
    const handleAction = jest.fn();

    render(
      <StandardCMSTable
        columns={columns}
        dataSource={mockData}
        onAction={handleAction}
        rowKey="id"
      />
    );

    const editButton = screen.getByLabelText('Edit article 1');
    fireEvent.click(editButton);

    await waitFor(() => {
      expect(handleAction).toHaveBeenCalledWith('edit', mockData[0]);
    });
  });
});

Hook Testing

import { renderHook, act } from '@testing-library/react-hooks';
import { useDialog } from '@rytass/cms-react-components';

describe('useDialog', () => {
  it('opens and closes dialog', () => {
    const { result } = renderHook(() => useDialog());

    act(() => {
      result.current.openDialog({
        title: 'Test Dialog',
        children: 'Test content',
      });
    });

    expect(result.current.isOpen).toBe(true);

    act(() => {
      result.current.closeDialog();
    });

    expect(result.current.isOpen).toBe(false);
  });
});

Migration Guide

From Legacy Components

// Before (legacy)
import { Table, Form, Modal } from 'legacy-cms-components';

// After (new)
import { 
  StandardCMSTable,
  StandardCMSFormActions,
  useModal 
} from '@rytass/cms-react-components';

// Update table props
const legacyTableProps = {
  data: articles,
  columns: columnConfig,
  onRowClick: handleClick,
};

const newTableProps = {
  dataSource: articles,
  columns: columnConfig,
  onAction: (action, record) => {
    if (action === 'view') handleClick(record);
  },
};

Troubleshooting

Common Issues

  • Modal not opening: Ensure ModalProvider is wrapping your component
  • Form validation not working: Check react-hook-form registration
  • Permissions not applying: Verify permission array format
  • Table not rendering: Check dataSource and column configuration

Debug Mode

import { StandardCMSTable } from '@rytass/cms-react-components';

// Enable debug mode for detailed logging
<StandardCMSTable
  {...tableProps}
  debug
  onDebugLog={(log) => console.log('Table debug:', log)}
/>

License

MIT

Keywords

rytass

FAQs

Package last updated on 19 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.