Built-in Components
Use pre-built React components from EZ-Console framework.
Overview
EZ-Console provides a set of built-in React components that simplify common development tasks. These components include the main application shell, permission guards, layouts, and more. This guide covers how to use these components in your applications.
Built-in Components
EZApp
The main application component that provides the entire app structure, including layout, navigation, authentication flow, and route protection.
import { EZApp } from 'ez-console';
export default function App() {
return <EZApp basePath='/' />;
}
Props
basePath(string): Base path for the application (e.g.,'/'or'/app')extraPrivateRoutes(Route[]): Additional private routes to add to the appextraPublicRoutes(Route[]): Additional public routes (e.g., login, register)
Example: Adding Custom Routes
import { EZApp, withSuspense } from 'ez-console';
import { lazy } from 'react';
const ProductPage = lazy(() => import('@/pages/ProductPage'));
const OrderPage = lazy(() => import('@/pages/OrderPage'));
export default function App() {
return (
<EZApp
basePath='/'
extraPrivateRoutes={[
{
path: '/products',
element: withSuspense(ProductPage),
name: 'products',
},
{
path: '/orders',
element: withSuspense(OrderPage),
name: 'orders',
},
]}
/>
);
}
Built-in Features
The EZApp component provides:
- ✅ Layout (header, sidebar, content area)
- ✅ Navigation menu with automatic route detection
- ✅ Authentication flow and route protection
- ✅ User profile dropdown menu
- ✅ Language switcher
- ✅ Global state providers (Auth, Theme, etc.)
PermissionGuard
Conditionally renders content based on user permissions. This component integrates with EZ-Console's RBAC system.
import { PermissionGuard } from 'ez-console';
export const ProductActions: React.FC = () => {
return (
<Space>
<PermissionGuard permission="product:create">
<Button type="primary">Create Product</Button>
</PermissionGuard>
<PermissionGuard permission="product:update">
<Button>Edit Product</Button>
</PermissionGuard>
<PermissionGuard permission="product:delete">
<Button danger>Delete Product</Button>
</PermissionGuard>
</Space>
);
};
Props
permission(string): Permission string in formatModule:Resource:Action(e.g.,"product:create")children(ReactNode): Content to render if permission is granted
Layout
Pre-built layout component with sidebar and header. This is automatically included in EZApp but can be used standalone if needed.
import { Layout } from 'ez-console';
// Already included in EZApp
// Customize through theme configuration or props
Loading
Simple loading spinner component for indicating loading states.
import { Loading } from 'ez-console';
export const MyComponent: React.FC = () => {
const { loading } = useRequest(() => apiGet('/data'));
if (loading) {
return <Loading />;
}
return <div>Content</div>;
};
Creating Reusable Components
While EZ-Console provides core components, you'll often want to create your own reusable components. Here are some common patterns:
DataTable Component
A reusable table component with action buttons:
// src/components/DataTable.tsx
import { Table, Button, Space } from 'antd';
import { TableProps } from 'antd/es/table';
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
interface DataTableProps<T> extends TableProps<T> {
onEdit?: (record: T) => void;
onDelete?: (record: T) => void;
canEdit?: boolean;
canDelete?: boolean;
}
export function DataTable<T extends { id: string }>({
onEdit,
onDelete,
canEdit = false,
canDelete = false,
columns,
...props
}: DataTableProps<T>) {
const actionColumn = {
title: 'Actions',
key: 'actions',
fixed: 'right' as const,
width: 150,
render: (_: any, record: T) => (
<Space>
{canEdit && onEdit && (
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => onEdit(record)}
>
Edit
</Button>
)}
{canDelete && onDelete && (
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => onDelete(record)}
>
Delete
</Button>
)}
</Space>
),
};
const finalColumns = [...(columns || []), actionColumn];
return <Table columns={finalColumns} {...props} />;
}
Usage
import { DataTable } from '@/components/DataTable';
export const ProductList: React.FC = () => {
const { data, loading } = useRequest(() => apiGet('/products'));
const handleEdit = (product: Product) => {
// Navigate to edit page
};
const handleDelete = (product: Product) => {
// Show delete confirmation
};
return (
<DataTable
dataSource={data}
loading={loading}
columns={[
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Price', dataIndex: 'price', key: 'price' },
]}
onEdit={handleEdit}
onDelete={handleDelete}
canEdit={true}
canDelete={true}
/>
);
};
FormModal Component
A reusable modal component with form handling:
// src/components/FormModal.tsx
import { Modal, Form, FormInstance } from 'antd';
import { ReactNode } from 'react';
interface FormModalProps {
title: string;
open: boolean;
loading?: boolean;
form: FormInstance;
onOk: () => void;
onCancel: () => void;
children: ReactNode;
width?: number;
}
export const FormModal: React.FC<FormModalProps> = ({
title,
open,
loading,
form,
onOk,
onCancel,
children,
width = 520,
}) => {
return (
<Modal
title={title}
open={open}
onOk={onOk}
onCancel={onCancel}
confirmLoading={loading}
width={width}
destroyOnClose
>
<Form form={form} layout="vertical">
{children}
</Form>
</Modal>
);
};
Usage
import { FormModal } from '@/components/FormModal';
export const ProductForm: React.FC = () => {
const [form] = Form.useForm();
const [open, setOpen] = useState(false);
const { run: createProduct, loading } = useRequest(
(values) => apiPost('/products', values),
{ manual: true, onSuccess: () => setOpen(false) }
);
const handleSubmit = () => {
form.validateFields().then((values) => {
createProduct(values);
});
};
return (
<FormModal
title="Create Product"
open={open}
loading={loading}
form={form}
onOk={handleSubmit}
onCancel={() => setOpen(false)}
>
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
</FormModal>
);
};
SearchForm Component
A reusable search form component:
// src/components/SearchForm.tsx
import { Form, Input, Button, Row, Col, Space } from 'antd';
import { FormInstance } from 'antd/es/form';
import { useTranslation } from 'ez-console';
interface SearchFormProps {
form: FormInstance;
onSearch: (values: any) => void;
onReset: () => void;
children?: React.ReactNode;
}
export const SearchForm: React.FC<SearchFormProps> = ({
form,
onSearch,
onReset,
children,
}) => {
const { t } = useTranslation();
return (
<Form form={form} layout="vertical" onFinish={onSearch}>
<Row gutter={16}>
{children}
</Row>
<Row style={{ marginTop: 16 }}>
<Col>
<Space>
<Button type="primary" htmlType="submit">
{t('common.search')}
</Button>
<Button onClick={onReset}>
{t('common.reset')}
</Button>
</Space>
</Col>
</Row>
</Form>
);
};
Usage
import { SearchForm } from '@/components/SearchForm';
export const ProductSearch: React.FC = () => {
const [form] = Form.useForm();
const handleSearch = (values: any) => {
// Perform search
};
const handleReset = () => {
form.resetFields();
handleSearch({});
};
return (
<SearchForm form={form} onSearch={handleSearch} onReset={handleReset}>
<Col span={8}>
<Form.Item name="name" label="Name">
<Input placeholder="Search by name" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="category" label="Category">
<Select placeholder="Select category">
<Option value="electronics">Electronics</Option>
<Option value="clothing">Clothing</Option>
</Select>
</Form.Item>
</Col>
</SearchForm>
);
};
ConfirmDelete Hook
A custom hook for delete confirmation dialogs:
// src/components/ConfirmDelete.tsx
import { Modal } from 'antd';
import { useTranslation } from 'ez-console';
import { ExclamationCircleOutlined } from '@ant-design/icons';
export const useConfirmDelete = () => {
const { t } = useTranslation();
return (onConfirm: () => void, itemName?: string) => {
Modal.confirm({
title: t('common.confirmDelete'),
icon: <ExclamationCircleOutlined />,
content: itemName
? t('common.confirmDeleteItem', { item: itemName })
: t('common.confirmDeleteMessage'),
okText: t('common.confirm'),
okType: 'danger',
cancelText: t('common.cancel'),
onOk: onConfirm,
});
};
};
Usage
import { useConfirmDelete } from '@/components/ConfirmDelete';
export const ProductList: React.FC = () => {
const confirmDelete = useConfirmDelete();
const { run: deleteProduct } = useRequest(
(id: string) => apiDelete(`/products/${id}`),
{ manual: true }
);
const handleDelete = (product: Product) => {
confirmDelete(() => {
deleteProduct(product.id);
}, product.name);
};
return (
<Button danger onClick={() => handleDelete(product)}>
Delete
</Button>
);
};
Component Patterns
List-Detail Pattern
A common pattern where clicking an item shows its details:
// src/pages/products/index.tsx
import { useState } from 'react';
import ProductList from './ProductList';
import ProductDetail from './ProductDetail';
export const ProductsPage: React.FC = () => {
const [selectedId, setSelectedId] = useState<string | null>(null);
if (selectedId) {
return (
<ProductDetail
id={selectedId}
onBack={() => setSelectedId(null)}
/>
);
}
return (
<ProductList
onSelect={(id) => setSelectedId(id)}
/>
);
};
Master-Detail Pattern
Using tabs to show different aspects of a single entity:
// src/pages/orders/OrderDetail.tsx
import { Tabs } from 'antd';
import OrderInfo from './OrderInfo';
import OrderItems from './OrderItems';
import OrderHistory from './OrderHistory';
export const OrderDetail: React.FC<{ id: string }> = ({ id }) => {
return (
<Tabs
items={[
{
key: 'info',
label: 'Order Information',
children: <OrderInfo id={id} />,
},
{
key: 'items',
label: 'Items',
children: <OrderItems id={id} />,
},
{
key: 'history',
label: 'History',
children: <OrderHistory id={id} />,
},
]}
/>
);
};
Form with Steps
Multi-step forms for complex data entry:
// src/pages/products/ProductWizard.tsx
import { Steps, Button, Card } from 'antd';
import { useState } from 'react';
export const ProductWizard: React.FC = () => {
const [current, setCurrent] = useState(0);
const [formData, setFormData] = useState({});
const steps = [
{
title: 'Basic Info',
content: <BasicInfoForm data={formData} onChange={setFormData} />,
},
{
title: 'Pricing',
content: <PricingForm data={formData} onChange={setFormData} />,
},
{
title: 'Images',
content: <ImagesForm data={formData} onChange={setFormData} />,
},
];
return (
<Card>
<Steps current={current} items={steps} style={{ marginBottom: 24 }} />
<div>{steps[current].content}</div>
<div style={{ marginTop: 24, textAlign: 'right' }}>
{current > 0 && (
<Button onClick={() => setCurrent(current - 1)} style={{ marginRight: 8 }}>
Previous
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)}>
Next
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={handleSubmit}>
Submit
</Button>
)}
</div>
</Card>
);
};
Styling Components
Using Ant Design Theme
Global theme customization through ConfigProvider:
// src/App.tsx
import { ConfigProvider } from 'antd';
import { EZApp } from 'ez-console';
export default function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
borderRadius: 6,
fontSize: 14,
},
components: {
Button: {
colorPrimary: '#00b96b',
},
Table: {
headerBg: '#fafafa',
},
},
}}
>
<EZApp basePath='/' />
</ConfigProvider>
);
}
Inline Styles
For component-specific styling:
<div style={{
padding: 24,
background: '#fff',
minHeight: 360,
borderRadius: 8,
}}>
Content
</div>
CSS Modules
For more complex styling needs:
// styles.module.css
.container {
padding: 24px;
background: #fff;
border-radius: 8px;
}
.title {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
}
// Component.tsx
import styles from './styles.module.css';
<div className={styles.container}>
<h2 className={styles.title}>Title</h2>
Content
</div>
Best Practices
1. Component Composition
Break down complex components into smaller, reusable pieces:
// ✅ Good: Composed components
<ProductCard>
<ProductImage />
<ProductInfo />
<ProductActions />
</ProductCard>
// ❌ Bad: Monolithic component
<ProductCardWithEverything />
2. TypeScript Typing
Always define TypeScript interfaces for component props:
// ✅ Good
interface ProductCardProps {
product: Product;
onEdit?: (product: Product) => void;
onDelete?: (id: string) => void;
}
// ❌ Bad
const ProductCard = (props: any) => { ... }
3. Default Props
Provide sensible defaults:
// ✅ Good
interface ButtonProps {
size?: 'small' | 'medium' | 'large';
variant?: 'primary' | 'secondary';
}
const Button = ({ size = 'medium', variant = 'primary', ...props }: ButtonProps) => {
// ...
};
4. Error Boundaries
Wrap components in error boundaries for better error handling:
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallback={<ErrorFallback />}>
<ProductList />
</ErrorBoundary>
5. Loading States
Always show loading indicators during async operations:
const { data, loading } = useRequest(() => apiGet('/products'));
if (loading) {
return <Loading />;
}
return <ProductList data={data} />;
6. Empty States
Handle empty data gracefully:
const { data } = useRequest(() => apiGet('/products'));
if (!data || data.length === 0) {
return <Empty description="No products found" />;
}
return <ProductList data={data} />;
Related Topics
- Application Shell - Learn about the EZApp component
- Routing & Navigation - Add routes to your application
- Forms & Validation - Create forms with validation
- Internationalization - Add multi-language support
- State Management - Manage application state
Next Steps
- Learn about Internationalization
- Explore API Integration
- Review Performance Optimization
Need help? Ask in GitHub Discussions.