uiXpress
Dashboard

System Features

This document outlines the core system features of the uiXpress Dashboard, including the date range picker, category system, card rendering, and extensibility mechanisms.

Date Range Picker

Location: app/src/pages/dashboard/src/date-range-picker.vue

Overview

The date range picker is a global control that affects all dashboard cards. When the date range changes, all cards automatically refresh their data to reflect the new time period.

Features

Predefined Ranges

Quick selection buttons for common date ranges:

  • Previous 7 days: Last 7 days including today
  • Previous 30 days: Last 30 days including today
  • This week to date: From Monday of current week to today
  • Last week: Complete previous week (Monday to Sunday)
  • This month to date: From 1st of current month to today
  • Last month: Complete previous month
  • This year to date: From January 1st to today
  • Last year: Complete previous year

Custom Date Selection

  • Calendar Interface: Interactive calendar for precise date selection
  • Range Selection: Click to select start date, click again for end date
  • Month Navigation: Previous/next month buttons
  • Year Selection: Dropdown for year selection
  • Auto-Apply: Changes apply immediately without confirmation

Visual Design

  • Date Pills: Selected dates displayed as pills with formatted dates
  • Calendar Icon: Visual indicator for date picker
  • Context Menu: Dropdown menu with predefined ranges and calendar
  • Dark Mode: Full dark mode support
  • Responsive: Adapts to mobile and desktop screens

Implementation

// Date range state
const dateRange = ref([
  new Date(new Date().setDate(new Date().getDate() - 7)),
  new Date(),
]);

// Handle date range changes
const handleDateRangeChange = (newDateRange) => {
  dateRange.value = newDateRange;
  // All cards automatically update via props
};

Date Formatting

Dates are formatted using browser locale:

const displayFormat = (date) => {
  const dateObj = new Date(date);
  return dateObj.toLocaleDateString(undefined, {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  });
};

Category System

Overview

The category system organizes dashboard cards into logical groups, allowing users to focus on specific aspects of their site management.

Default Categories

  1. Overview (site)
    • Site management and content insights
    • Recent posts, comments, media, users
    • Server health monitoring
  2. Analytics (analytics)
    • Website traffic and visitor analytics
    • Page views, devices, countries
    • Engagement metrics

Category Registration

Categories are registered via filter hook:

addFilter('uixpress/dashboard/categories/register', (categories) => {
  return [
    ...categories,
    { value: 'site', label: __('Overview', 'uixpress') },
    { value: 'analytics', label: __('Analytics', 'uixpress') },
  ];
});

Category Navigation

  • Tab Interface: Toggle buttons for switching categories
  • URL Persistence: Category stored in URL query parameter
  • State Management: Active category tracked in component state
  • Smooth Transitions: Cards fade in/out when switching categories

URL Structure

/wp-admin/index.php?dashboard_category=analytics

The category parameter persists across page reloads and can be bookmarked.

Implementation

// Active category from URL or default
const activeCategoryID = computed(() => {
  const urlParams = new URLSearchParams(window.location.search);
  const dashboardCategory = urlParams.get('dashboard_category');
  
  if (dashboardCategory) {
    const siteCategory = registeredCategories.value.find(
      (category) => category.value === dashboardCategory
    )?.value;
    if (siteCategory) return siteCategory;
  }
  
  return registeredCategories.value[0]?.value || null;
});

// Update URL when category changes
watch(activeCategory, (newVal) => {
  const urlParams = new URLSearchParams(window.location.search);
  urlParams.set('dashboard_category', newVal);
  window.history.replaceState(
    {},
    '',
    `${window.location.pathname}?${urlParams.toString()}`
  );
});

Card Rendering System

Location: app/src/pages/dashboard/src/card-render.vue

Overview

The card renderer is responsible for displaying dashboard cards based on their configuration and user capabilities.

Features

Single Cards

  • Component Rendering: Renders Vue component for each card
  • Grid Layout: Applies column span based on card width
  • Responsive: Mobile width override for small screens
  • Props Passing: Passes dateRange and appData to card components

Group Cards

  • Nested Layout: Groups can contain multiple cards
  • Column Configuration: Groups define their own column layout
  • Recursive Rendering: Groups can contain other groups

Capability Checking

Cards can require specific WordPress capabilities:

const userHasCapability = (capabilities) => {
  if (!capabilities) return true;
  if (!Array.isArray(capabilities)) return true;
  if (!appStore.state.currentUser) return false;

  for (let capability of capabilities) {
    const hasCap = appStore.state.currentUser.allcaps[capability];
    if (hasCap) return true;
  }
  return false;
};

Grid System

Cards use a 12-column CSS Grid:

<component
  :is="card.component"
  :class="`col-span-${card.metadata.width || 4} max-md:col-span-${
    card.metadata.mobileWidth || 12
  }`"
/>

Card Widths

  • Desktop: 1-12 columns (default: 4)
  • Mobile: 1-12 columns (default: 12)
  • Responsive: Automatically stacks on mobile devices

Extensibility System

Hook System

The dashboard uses a filter-based hook system for extensibility:

import { applyFilters } from '@/assets/js/functions/HooksSystem.js';

// Register cards
registeredCards.value = await applyFilters(
  'uixpress/dashboard/cards/register',
  registeredCards.value
);

// Register categories
registeredCategories.value = await applyFilters(
  'uixpress/dashboard/categories/register',
  registeredCategories.value
);

Event System

Custom events for plugin integration:

// Dispatch when dashboard is ready
const event = new CustomEvent('uixpress/dashboard/ready');
document.dispatchEvent(event);

External Plugin Integration

External plugins can register cards:

document.addEventListener('uixpress/dashboard/ready', () => {
  const { addFilter } = window.uixpress;
  
  addFilter('uixpress/dashboard/cards/register', (widgets) => {
    return [...widgets, myExternalCard];
  });
});

Card Registration

Cards are registered via filter:

import { addFilter } from '@/assets/js/functions/HooksSystem.js';
import myCard from './my-card/index.js';

addFilter('uixpress/dashboard/cards/register', (widgets) => {
  return [...widgets, myCard];
});

Shadow DOM Isolation

Overview

The dashboard uses Shadow DOM to isolate its styles from WordPress admin styles, preventing conflicts.

Implementation

<ShadowRoot
  tag="div"
  :adopted-style-sheets="[adoptedStyleSheets]"
  class="uipx-normalize"
>
  <!-- Dashboard content -->
</ShadowRoot>

Style Injection

Styles are injected into the Shadow DOM:

const setStyles = () => {
  let appStyleNode = document.querySelector('#uixpress-dashboard-css');
  if (!appStyleNode) {
    appStyleNode = manuallyAddStyleSheet();
    appStyleNode.onload = () => {
      const appStyles = appStyleNode.sheet;
      for (const rule of [...appStyles.cssRules].reverse()) {
        adoptedStyleSheets.value.insertRule(rule.cssText);
      }
    };
  } else {
    const appStyles = appStyleNode.sheet;
    for (const rule of [...appStyles.cssRules].reverse()) {
      adoptedStyleSheets.value.insertRule(rule.cssText);
    }
  }
};

Benefits

  • Style Isolation: No conflicts with WordPress admin styles
  • Encapsulation: Dashboard styles don't leak to other admin pages
  • Consistency: Ensures dashboard looks consistent regardless of theme

Dark Mode Support

Overview

The dashboard includes full dark mode support using Tailwind CSS dark mode variants.

Implementation

<div :class="isDark ? 'dark' : ''" class="...">
  <!-- Dashboard content -->
</div>

Dark Mode Detection

import { useDarkMode } from './src/useDarkMode.js';
const { isDark } = useDarkMode();

Color Scheme

  • Background: bg-zinc-800/20 (dark), bg-white (light)
  • Text: text-zinc-400 (dark), text-zinc-500 (light)
  • Borders: border-zinc-700/30 (dark), border-zinc-200/50 (light)
  • Cards: bg-zinc-900/50 (dark), bg-zinc-50 (light)

Loading States

Global Loading Indicator

Top loading bar shows during initial page load:

<LoadingIndicator :height="3" />

Card Loading States

Each card manages its own loading state:

const loading = ref(false);

const loadData = async () => {
  loading.value = true;
  try {
    // Fetch data
  } finally {
    loading.value = false;
  }
};

Visual Feedback

  • Loading Spinner: Shows during data fetching
  • Skeleton Screens: Placeholder content while loading
  • Progress Indicators: Visual feedback for long operations

Error Handling

Card-Level Errors

Each card handles its own errors:

const error = ref(null);

try {
  // Fetch data
} catch (err) {
  error.value = err.message || 'Failed to load data';
}

Error Display

<div v-if="error" class="error-message">
  {{ error }}
  <button @click="loadData">Retry</button>
</div>

Global Error Handling

  • API Errors: Caught and displayed in cards
  • Network Errors: Retry mechanisms
  • Permission Errors: Capability checks prevent errors

Performance Optimizations

Lazy Loading

Cards load data asynchronously:

onMounted(async () => {
  await loadData();
});

Date Range Watchers

Efficient watching of date range changes:

watch(
  () => props.dateRange,
  loadData,
  { deep: true }
);

Debouncing

Consider debouncing rapid date range changes to prevent excessive API calls.

Caching

Analytics data may be cached to reduce database queries.


Accessibility

Semantic HTML

  • Proper heading hierarchy
  • ARIA labels where needed
  • Keyboard navigation support

Screen Reader Support

  • Descriptive labels
  • Status announcements
  • Error messages

Keyboard Navigation

  • Tab navigation through cards
  • Enter/Space for interactive elements
  • Escape to close modals