Skip to content

React Version โ€‹

HaloLight React version is built on React 19 + Vite 6, a pure Client-Side Rendering (CSR) Single Page Application (SPA).

Live Preview: https://halolight-react.h7ml.cn/

GitHub: https://github.com/halolight/halolight-react

Features โ€‹

  • ๐Ÿ—๏ธ React 19 - Latest React features and performance optimizations
  • โšก Vite 6 - Lightning-fast cold start and HMR
  • ๐ŸŽจ Theme System - 11 skins, dark mode, View Transitions
  • ๐Ÿ” Authentication - Complete login/register/password recovery flow
  • ๐Ÿ“Š Dashboard - Data visualization and business management
  • ๐Ÿ›ก๏ธ Permission Control - RBAC fine-grained permission management
  • ๐Ÿ“‘ Multi-tab - Browser-style tab management
  • โŒ˜ Command Palette - Keyboard shortcuts navigation (โŒ˜K)

Tech Stack โ€‹

TechnologyVersionDescription
React19.xUI framework
Vite6.xBuild tool
TypeScript5.xType safety
React Router6.xClient-side routing
Zustand5.xState management
TanStack Query5.xServer state
React Hook Form7.xForm handling
Zod4.xData validation
Tailwind CSS4.xAtomic CSS
shadcn/uilatestUI component library
react-grid-layout1.5.xDrag-and-drop layout
Recharts3.xChart visualization
Framer Motion12.xAnimation effects
Mock.js1.xData mocking

Core Features โ€‹

  • Configurable Dashboard - 9 widgets, drag-and-drop layout, responsive design
  • Multi-tab Navigation - Browser-style tabs, context menu, state caching
  • Permission System - RBAC permission control, route guards, permission components
  • Theme System - 11 skins, dark mode, View Transitions
  • Multi-account Switching - Quick account switching, remember login state
  • Command Palette - Keyboard shortcuts (โŒ˜K), global search
  • Real-time Notifications - WebSocket push, notification center

Directory Structure โ€‹

halolight-react/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ pages/                     # Page components
โ”‚   โ”‚   โ”œโ”€โ”€ auth/                  # Auth pages
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ login/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ register/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ forgot-password/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ reset-password/
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/             # Dashboard
โ”‚   โ”‚   โ””โ”€โ”€ legal/                 # Legal pages
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ ui/                    # shadcn/ui components (20+)
โ”‚   โ”‚   โ”œโ”€โ”€ layout/                # Layout components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ admin-layout.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ auth-layout.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ sidebar.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ header.tsx
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ footer.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/             # Dashboard components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ configurable-dashboard.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ widget-wrapper.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ stats-widget.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ chart-widget.tsx
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚   โ””โ”€โ”€ shared/                # Shared components
โ”‚   โ”œโ”€โ”€ hooks/                     # Custom Hooks
โ”‚   โ”‚   โ”œโ”€โ”€ use-users.ts
โ”‚   โ”‚   โ”œโ”€โ”€ use-auth.ts
โ”‚   โ”‚   โ”œโ”€โ”€ use-theme.ts
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ stores/                    # Zustand Stores
โ”‚   โ”‚   โ”œโ”€โ”€ auth.ts
โ”‚   โ”‚   โ”œโ”€โ”€ ui-settings.ts
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard-layout.ts
โ”‚   โ”‚   โ””โ”€โ”€ tabs.ts
โ”‚   โ”œโ”€โ”€ lib/
โ”‚   โ”‚   โ”œโ”€โ”€ api/                   # API services
โ”‚   โ”‚   โ”œโ”€โ”€ auth/                  # Auth logic
โ”‚   โ”‚   โ”œโ”€โ”€ validations/           # Zod schemas
โ”‚   โ”‚   โ””โ”€โ”€ utils.ts               # Utility functions
โ”‚   โ”œโ”€โ”€ routes/                    # Route configuration
โ”‚   โ”‚   โ””โ”€โ”€ index.tsx
โ”‚   โ”œโ”€โ”€ config/                    # Configuration files
โ”‚   โ”‚   โ”œโ”€โ”€ routes.ts
โ”‚   โ”‚   โ””โ”€โ”€ tdk.ts
โ”‚   โ”œโ”€โ”€ types/                     # Type definitions
โ”‚   โ”œโ”€โ”€ mock/                      # Mock data
โ”‚   โ”œโ”€โ”€ providers/                 # Context Providers
โ”‚   โ”œโ”€โ”€ App.tsx
โ”‚   โ””โ”€โ”€ main.tsx
โ”œโ”€โ”€ public/                        # Static assets
โ”œโ”€โ”€ vite.config.ts
โ”œโ”€โ”€ tsconfig.json
โ””โ”€โ”€ package.json

Quick Start โ€‹

Environment Requirements โ€‹

  • Node.js >= 18.0.0
  • pnpm >= 9.x

Installation โ€‹

bash
git clone https://github.com/halolight/halolight-react.git
cd halolight-react
pnpm install

Environment Variables โ€‹

bash
cp .env.example .env.development
env
# .env.development example
VITE_API_URL=/api
VITE_MOCK=true
VITE_APP_TITLE=Admin Pro
VITE_BRAND_NAME=Halolight
VITE_DEMO_EMAIL=admin@halolight.h7ml.cn
VITE_DEMO_PASSWORD=123456
VITE_SHOW_DEMO_HINT=true

Start Development โ€‹

bash
pnpm dev

Visit http://localhost:5173

Build for Production โ€‹

bash
pnpm build
pnpm preview

Demo Account โ€‹

RoleEmailPassword
Adminadmin@halolight.h7ml.cn123456
Useruser@halolight.h7ml.cn123456

Core Functionality โ€‹

State Management (Zustand) โ€‹

tsx
// stores/auth.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface AuthState {
  user: User | null
  token: string | null
  isAuthenticated: boolean
  login: (credentials: LoginCredentials) => Promise<void>
  logout: () => void
  hasPermission: (permission: string) => boolean
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isAuthenticated: false,

      login: async (credentials) => {
        const response = await authApi.login(credentials)
        set({
          user: response.user,
          token: response.token,
          isAuthenticated: true,
        })
      },

      logout: () => {
        set({ user: null, token: null, isAuthenticated: false })
      },

      hasPermission: (permission) => {
        const { user } = get()
        if (!user) return false
        return user.permissions.some(p =>
          p === '*' || p === permission ||
          (p.endsWith(':*') && permission.startsWith(p.slice(0, -1)))
        )
      },
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({ token: state.token, user: state.user }),
    }
  )
)

Data Fetching (TanStack Query) โ€‹

tsx
// hooks/use-users.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { usersApi } from '@/lib/api'

export function useUsers(params?: UserQueryParams) {
  return useQuery({
    queryKey: ['users', params],
    queryFn: () => usersApi.getList(params),
  })
}

export function useCreateUser() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: usersApi.create,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] })
    },
  })
}

// Usage in component
function UsersPage() {
  const { data: users, isLoading, error } = useUsers()

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return <div>{/* Render user list */}</div>
}

Permission Control โ€‹

tsx
// hooks/use-permission.ts
import { useAuthStore } from '@/stores/auth'

export function usePermission(permission: string): boolean {
  const hasPermission = useAuthStore((state) => state.hasPermission)
  return hasPermission(permission)
}

export function usePermissions(permissions: string[]): boolean {
  const hasPermission = useAuthStore((state) => state.hasPermission)
  return permissions.every(p => hasPermission(p))
}
tsx
// Usage
function DeleteButton() {
  const canDelete = usePermission('users:delete')

  if (!canDelete) return null

  return <Button variant="destructive">Delete</Button>
}
tsx
// components/permission-guard.tsx
import { usePermission } from '@/hooks/use-permission'

interface PermissionGuardProps {
  permission: string
  children: React.ReactNode
  fallback?: React.ReactNode
}

export function PermissionGuard({
  permission,
  children,
  fallback = null,
}: PermissionGuardProps) {
  const hasPermission = usePermission(permission)

  if (!hasPermission) return fallback

  return <>{children}</>
}
tsx
<!-- Usage -->
<PermissionGuard permission="users:delete" fallback={<span>No permission</span>}>
  <DeleteButton />
</PermissionGuard>

Draggable Dashboard โ€‹

tsx
// components/dashboard/configurable-dashboard.tsx
import GridLayout from 'react-grid-layout'
import { useDashboardStore } from '@/stores/dashboard-layout'

export function ConfigurableDashboard() {
  const { layout, setLayout, isEditing } = useDashboardStore()

  return (
    <GridLayout
      layout={layout}
      onLayoutChange={setLayout}
      cols={12}
      rowHeight={80}
      isDraggable={isEditing}
      isResizable={isEditing}
      margin={[16, 16]}
    >
      {layout.map((item) => (
        <div key={item.i}>
          <WidgetWrapper widget={getWidget(item.i)} />
        </div>
      ))}
    </GridLayout>
  )
}

Theme System โ€‹

Skin Presets โ€‹

Supports 11 preset skins, switchable via quick settings panel:

SkinMain ColorCSS Variable
DefaultPurple--primary: 51.1% 0.262 276.97
BlueBlue--primary: 54.8% 0.243 264.05
EmeraldEmerald--primary: 64.6% 0.178 142.49
OrangeOrange--primary: 65.7% 0.198 45.13
RoseRose--primary: 58.9% 0.238 11.26
CyanCyan--primary: 75.6% 0.146 191.68
YellowYellow--primary: 85.1% 0.184 98.08
VioletViolet--primary: 55.3% 0.264 293.49
SlateSlate--primary: 47.9% 0.017 256.71
ZincZinc--primary: 48.3% 0 0
NeutralNeutral--primary: 48.5% 0 0

CSS Variables (OKLch) โ€‹

css
/* Example variable definitions */
:root {
  --background: 100% 0 0;
  --foreground: 14.9% 0.017 285.75;
  --primary: 51.1% 0.262 276.97;
  --primary-foreground: 100% 0 0;
  --secondary: 96.1% 0.004 286.41;
  --secondary-foreground: 14.9% 0.017 285.75;
  --muted: 96.1% 0.004 286.41;
  --muted-foreground: 45.8% 0.009 285.77;
  --accent: 96.1% 0.004 286.41;
  --accent-foreground: 14.9% 0.017 285.75;
  --destructive: 59.3% 0.246 27.33;
  --destructive-foreground: 100% 0 0;
  --border: 89.8% 0.006 286.32;
  --input: 89.8% 0.006 286.32;
  --ring: 51.1% 0.262 276.97;
  --radius: 0.5rem;
}

.dark {
  --background: 0% 0 0;
  --foreground: 98.3% 0 0;
  --primary: 51.1% 0.262 276.97;
  --primary-foreground: 100% 0 0;
  /* ... */
}

Page Routes โ€‹

PathPagePermission
/Redirect to /dashboard-
/loginLoginPublic
/registerRegisterPublic
/forgot-passwordForgot passwordPublic
/reset-passwordReset passwordPublic
/dashboardDashboarddashboard:view
/usersUser listusers:list
/users/createCreate userusers:create
/users/:idUser detailsusers:view
/users/:id/editEdit userusers:update
/rolesRole managementroles:list
/permissionsPermission managementpermissions:list
/settingsSystem settingssettings:view
/profileUser profileAuthenticated

Environment Variables โ€‹

Configuration Example โ€‹

bash
cp .env.example .env.development
env
# .env.development example
VITE_API_URL=/api
VITE_MOCK=true
VITE_APP_TITLE=Admin Pro
VITE_BRAND_NAME=Halolight
VITE_DEMO_EMAIL=admin@halolight.h7ml.cn
VITE_DEMO_PASSWORD=123456
VITE_SHOW_DEMO_HINT=true

Variable Descriptions โ€‹

Variable NameDescriptionDefault Value
VITE_API_URLAPI base path/api
VITE_MOCKEnable Mock datatrue
VITE_APP_TITLEApplication titleAdmin Pro
VITE_BRAND_NAMEBrand nameHalolight
VITE_DEMO_EMAILDemo account emailadmin@halolight.h7ml.cn
VITE_DEMO_PASSWORDDemo account password123456
VITE_SHOW_DEMO_HINTShow demo hinttrue

Usage โ€‹

tsx
// Access environment variables in code
const apiUrl = import.meta.env.VITE_API_URL
const isMock = import.meta.env.VITE_MOCK === 'true'

Common Commands โ€‹

bash
pnpm dev            # Start development server
pnpm build          # Production build
pnpm preview        # Preview production build
pnpm lint           # Code linting
pnpm lint:fix       # Auto fix
pnpm type-check     # Type checking
pnpm test           # Run tests
pnpm test:coverage  # Test coverage

Testing โ€‹

bash
pnpm test           # Run tests (watch mode)
pnpm test:run       # Single run
pnpm test:coverage  # Coverage report
pnpm test:ui        # Vitest UI interface

Test Examples โ€‹

tsx
// __tests__/components/Button.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Button } from '@/components/ui/button'

describe('Button', () => {
  it('renders button with text', () => {
    render(<Button>Click me</Button>)
    expect(screen.getByRole('button')).toHaveTextContent('Click me')
  })

  it('handles click events', async () => {
    const handleClick = vi.fn()
    render(<Button onClick={handleClick}>Click me</Button>)

    await userEvent.click(screen.getByRole('button'))
    expect(handleClick).toHaveBeenCalledTimes(1)
  })

  it('disables button when disabled prop is true', () => {
    render(<Button disabled>Click me</Button>)
    expect(screen.getByRole('button')).toBeDisabled()
  })
})

Configuration โ€‹

Vite Configuration โ€‹

ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
  build: {
    outDir: 'dist',
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
          'chart-vendor': ['recharts'],
        },
      },
    },
  },
})

Deployment โ€‹

Deploy with Vercel

bash
vercel

Docker โ€‹

dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
bash
docker build -t halolight-react .
docker run -p 3000:80 halolight-react

Other Platforms โ€‹

CI/CD โ€‹

Complete GitHub Actions CI workflow configuration:

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm type-check

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test:coverage
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm audit --audit-level=high

Advanced Features โ€‹

PWA Support โ€‹

Built-in PWA support including:

  • Service Worker registration
  • Offline caching
  • App manifest (manifest.json)
  • Multiple icon sizes
json
// public/manifest.json
{
  "name": "Admin Pro",
  "short_name": "Admin",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

React Router Configuration โ€‹

tsx
// routes/index.tsx
import { createBrowserRouter, Navigate } from 'react-router-dom'
import { DashboardLayout } from '@/layouts/dashboard-layout'
import { AuthLayout } from '@/layouts/auth-layout'

export const router = createBrowserRouter([
  {
    path: '/',
    element: <Navigate to="/dashboard" replace />,
  },
  {
    path: '/login',
    element: <AuthLayout />,
    children: [
      { index: true, element: <LoginPage /> },
    ],
  },
  {
    path: '/',
    element: <DashboardLayout />,
    children: [
      { path: 'dashboard', element: <HomePage /> },
      { path: 'users', element: <UsersPage /> },
      { path: 'settings', element: <SettingsPage /> },
      // More routes...
    ],
  },
])

Route Guards โ€‹

tsx
// components/auth-guard.tsx
import { Navigate, useLocation } from 'react-router-dom'
import { useAuthStore } from '@/stores/auth'

interface AuthGuardProps {
  children: React.ReactNode
  permission?: string
}

export function AuthGuard({ children, permission }: AuthGuardProps) {
  const location = useLocation()
  const { isAuthenticated, hasPermission } = useAuthStore()

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />
  }

  if (permission && !hasPermission(permission)) {
    return <Navigate to="/403" replace />
  }

  return <>{children}</>
}

Performance Optimization โ€‹

Image Optimization โ€‹

tsx
// Lazy load images
import { useState } from 'react'

function LazyImage({ src, alt }: { src: string; alt: string }) {
  const [loaded, setLoaded] = useState(false)

  return (
    <div className="relative">
      {!loaded && <div className="skeleton" />}
      <img
        src={src}
        alt={alt}
        loading="lazy"
        onLoad={() => setLoaded(true)}
        className={loaded ? 'opacity-100' : 'opacity-0'}
      />
    </div>
  )
}

Lazy Loading Components โ€‹

tsx
// Route-level code splitting
import { lazy, Suspense } from 'react'

const Dashboard = lazy(() => import('@/pages/dashboard'))
const Users = lazy(() => import('@/pages/users'))

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/users" element={<Users />} />
      </Routes>
    </Suspense>
  )
}

Preloading โ€‹

tsx
// Preload component on hover
import { lazy } from 'react'

const UserDetails = lazy(() => import('@/pages/user-details'))

function UserList() {
  const preloadUserDetails = () => {
    // Trigger preload
    import('@/pages/user-details')
  }

  return (
    <Link
      to="/users/1"
      onMouseEnter={preloadUserDetails}
    >
      View Details
    </Link>
  )
}

Memo Optimization โ€‹

tsx
import { memo } from 'react'

// Prevent unnecessary re-renders
const ExpensiveComponent = memo(({ data }: { data: any }) => {
  return <div>{/* Complex rendering logic */}</div>
})

Frequently Asked Questions โ€‹

Q: How to add a new route? โ€‹

A: Add route configuration in src/routes/index.tsx:

tsx
{
  path: '/new-page',
  element: <NewPage />,
}

Q: How to customize theme colors? โ€‹

A: Modify CSS variables or use theme switching feature:

css
:root {
  --primary: 51.1% 0.262 276.97; /* Modify primary color */
}

Q: How to integrate real API? โ€‹

A: Set VITE_MOCK to false and configure VITE_API_URL:

env
VITE_MOCK=false
VITE_API_URL=https://api.example.com

Q: How to add new permissions? โ€‹

A: Add permission string to user's permissions array and use usePermission Hook:

tsx
const canEdit = usePermission('users:edit')

Comparison with Other Versions โ€‹

FeatureReact VersionNext.jsVue
SSR/SSGโŒโœ…โœ… (Nuxt)
State ManagementZustandZustandPinia
RoutingReact RouterApp RouterVue Router
Build ToolViteNext.jsVite