
Security News
rv Is a New Rust-Powered Ruby Version Manager Inspired by Python's uv
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
@rytass/cms-react-components
Advanced tools
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.
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
// 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;
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),
}}
/>
);
}
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>
);
}
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>
);
}
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>;
}
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>;
}
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>;
}
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}
/>
);
}
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>
);
}
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>
);
}
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;
};
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;
}
}
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>;
}
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>
);
}
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-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;
}
}
}
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
};
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}
/>
);
}
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>
);
}
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',
}}
/>
);
}
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]);
});
});
});
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);
});
});
// 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);
},
};
import { StandardCMSTable } from '@rytass/cms-react-components';
// Enable debug mode for detailed logging
<StandardCMSTable
{...tableProps}
debug
onDebugLog={(log) => console.log('Table debug:', log)}
/>
MIT
FAQs
Rytass Content Management System React Components
The npm package @rytass/cms-react-components receives a total of 14 weekly downloads. As such, @rytass/cms-react-components popularity was classified as not popular.
We found that @rytass/cms-react-components demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
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.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.
Security News
AGENTS.md is a fast-growing open format giving AI coding agents a shared, predictable way to understand project setup, style, and workflows.