Location: app/src/pages/dashboard/src/date-range-picker.vue
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.
Quick selection buttons for common date ranges:
// 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
};
Dates are formatted using browser locale:
const displayFormat = (date) => {
const dateObj = new Date(date);
return dateObj.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
});
};
The category system organizes dashboard cards into logical groups, allowing users to focus on specific aspects of their site management.
site)analytics)Categories are registered via filter hook:
addFilter('uixpress/dashboard/categories/register', (categories) => {
return [
...categories,
{ value: 'site', label: __('Overview', 'uixpress') },
{ value: 'analytics', label: __('Analytics', 'uixpress') },
];
});
/wp-admin/index.php?dashboard_category=analytics
The category parameter persists across page reloads and can be bookmarked.
// 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()}`
);
});
Location: app/src/pages/dashboard/src/card-render.vue
The card renderer is responsible for displaying dashboard cards based on their configuration and user capabilities.
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;
};
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
}`"
/>
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
);
Custom events for plugin integration:
// Dispatch when dashboard is ready
const event = new CustomEvent('uixpress/dashboard/ready');
document.dispatchEvent(event);
External plugins can register cards:
document.addEventListener('uixpress/dashboard/ready', () => {
const { addFilter } = window.uixpress;
addFilter('uixpress/dashboard/cards/register', (widgets) => {
return [...widgets, myExternalCard];
});
});
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];
});
The dashboard uses Shadow DOM to isolate its styles from WordPress admin styles, preventing conflicts.
<ShadowRoot
tag="div"
:adopted-style-sheets="[adoptedStyleSheets]"
class="uipx-normalize"
>
<!-- Dashboard content -->
</ShadowRoot>
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);
}
}
};
The dashboard includes full dark mode support using Tailwind CSS dark mode variants.
<div :class="isDark ? 'dark' : ''" class="...">
<!-- Dashboard content -->
</div>
import { useDarkMode } from './src/useDarkMode.js';
const { isDark } = useDarkMode();
bg-zinc-800/20 (dark), bg-white (light)text-zinc-400 (dark), text-zinc-500 (light)border-zinc-700/30 (dark), border-zinc-200/50 (light)bg-zinc-900/50 (dark), bg-zinc-50 (light)Top loading bar shows during initial page load:
<LoadingIndicator :height="3" />
Each card manages its own loading state:
const loading = ref(false);
const loadData = async () => {
loading.value = true;
try {
// Fetch data
} finally {
loading.value = false;
}
};
Each card handles its own errors:
const error = ref(null);
try {
// Fetch data
} catch (err) {
error.value = err.message || 'Failed to load data';
}
<div v-if="error" class="error-message">
{{ error }}
<button @click="loadData">Retry</button>
</div>
Cards load data asynchronously:
onMounted(async () => {
await loadData();
});
Efficient watching of date range changes:
watch(
() => props.dateRange,
loadData,
{ deep: true }
);
Consider debouncing rapid date range changes to prevent excessive API calls.
Analytics data may be cached to reduce database queries.