Standardize global empty-state UI component
This commit is contained in:
@@ -28,6 +28,7 @@ return [
|
||||
'css/components/app-details-titlebar.css',
|
||||
'css/components/app-details-validation-summary.css',
|
||||
'css/components/app-dashboard-titlebar.css',
|
||||
'css/components/app-empty-state.css',
|
||||
'css/components/app-details.css',
|
||||
'css/components/app-details-card.css',
|
||||
'css/components/app-list-table.css',
|
||||
|
||||
@@ -257,7 +257,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="app-stats-table">
|
||||
@@ -291,7 +291,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -546,7 +546,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($canViewUserLifecycleAudit && $userLifecycleAuditStatsAvailable): ?>
|
||||
@@ -569,12 +569,12 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -625,7 +625,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($canViewSystemAudit && $systemAuditStatsAvailable): ?>
|
||||
@@ -650,12 +650,12 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -806,7 +806,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="app-stats-table-explanation">
|
||||
@@ -916,7 +916,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -948,7 +948,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1068,7 +1068,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1109,7 +1109,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1137,7 +1137,7 @@ require templatePath('partials/app-breadcrumb.phtml');
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="app-stats-table-empty">
|
||||
<small class="muted"><?php e(t('No entries found')); ?></small>
|
||||
<?php $emptyState = ['message' => t('No entries found'), 'size' => 'compact']; require templatePath('partials/app-empty-state.phtml'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -282,7 +282,13 @@ $ssoSyncNeedsGraph = !empty($ssoUiState['sync_needs_graph']);
|
||||
</details>
|
||||
<hr>
|
||||
<?php if (!$customFieldDefinitions): ?>
|
||||
<p class="muted"><?php e(t('No custom fields configured yet')); ?></p>
|
||||
<?php
|
||||
$emptyState = [
|
||||
'message' => t('No custom fields configured yet'),
|
||||
'size' => 'compact',
|
||||
];
|
||||
require templatePath('partials/app-empty-state.phtml');
|
||||
?>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($customFieldDefinitions as $definition): ?>
|
||||
<?php
|
||||
|
||||
@@ -678,15 +678,25 @@ foreach ($tenants as $tenant) {
|
||||
<p class="muted"><?php e(t('Assign tenants first to edit custom fields')); ?></p>
|
||||
<?php else: ?>
|
||||
<?php $hasRenderedCustomFields = false; ?>
|
||||
<?php $hasConfiguredTenantDefinitions = false; ?>
|
||||
<?php foreach ($customFieldTenantMap as $tenantId => $tenant): ?>
|
||||
<?php $tenantDefinitions = $customFieldDefinitionsByTenant[$tenantId] ?? []; ?>
|
||||
<?php if ($tenantDefinitions): ?>
|
||||
<?php $hasConfiguredTenantDefinitions = true; ?>
|
||||
<?php endif; ?>
|
||||
<details name="custom-fields-tenant" data-details-key="custom-fields-tenant-<?php e((string) $tenantId); ?>">
|
||||
<summary>
|
||||
<?php e((string) ($tenant['description'] ?? t('Custom fields for tenant'))); ?>
|
||||
</summary>
|
||||
<hr>
|
||||
<?php if (!$tenantDefinitions): ?>
|
||||
<p class="muted"><?php e(t('No custom fields configured for this tenant')); ?></p>
|
||||
<?php
|
||||
$emptyState = [
|
||||
'message' => t('No custom fields configured for this tenant'),
|
||||
'size' => 'compact',
|
||||
];
|
||||
require templatePath('partials/app-empty-state.phtml');
|
||||
?>
|
||||
<?php else: ?>
|
||||
<div>
|
||||
<?php foreach ($tenantDefinitions as $definition): ?>
|
||||
@@ -829,8 +839,14 @@ foreach ($tenants as $tenant) {
|
||||
</details>
|
||||
<hr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (!$hasRenderedCustomFields): ?>
|
||||
<p class="muted"><?php e(t('No custom fields configured for selected tenants')); ?></p>
|
||||
<?php if (!$hasRenderedCustomFields && $hasConfiguredTenantDefinitions): ?>
|
||||
<?php
|
||||
$emptyState = [
|
||||
'message' => t('No custom fields configured for selected tenants'),
|
||||
'size' => 'compact',
|
||||
];
|
||||
require templatePath('partials/app-empty-state.phtml');
|
||||
?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
58
templates/partials/app-empty-state.phtml
Normal file
58
templates/partials/app-empty-state.phtml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Shared empty-state component.
|
||||
*
|
||||
* @var array<string, mixed> $emptyState
|
||||
*/
|
||||
|
||||
$emptyState = is_array($emptyState ?? null) ? $emptyState : [];
|
||||
$emptyMessage = trim((string) ($emptyState['message'] ?? ''));
|
||||
if ($emptyMessage === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$emptyHint = trim((string) ($emptyState['hint'] ?? ''));
|
||||
$emptySize = strtolower(trim((string) ($emptyState['size'] ?? 'default')));
|
||||
if (!in_array($emptySize, ['default', 'compact'], true)) {
|
||||
$emptySize = 'default';
|
||||
}
|
||||
|
||||
$emptyAlign = strtolower(trim((string) ($emptyState['align'] ?? 'center')));
|
||||
if (!in_array($emptyAlign, ['center', 'left'], true)) {
|
||||
$emptyAlign = 'center';
|
||||
}
|
||||
|
||||
$emptyAction = is_array($emptyState['action'] ?? null) ? $emptyState['action'] : [];
|
||||
$emptyActionLabel = trim((string) ($emptyAction['label'] ?? ''));
|
||||
$emptyActionHref = trim((string) ($emptyAction['href'] ?? ''));
|
||||
$emptyActionForm = trim((string) ($emptyAction['form'] ?? ''));
|
||||
$emptyActionClass = trim((string) ($emptyAction['class'] ?? 'secondary outline small'));
|
||||
$emptyActionTarget = trim((string) ($emptyAction['target'] ?? ''));
|
||||
$emptyActionRel = trim((string) ($emptyAction['rel'] ?? ''));
|
||||
$showAction = $emptyActionLabel !== '' && ($emptyActionHref !== '' || $emptyActionForm !== '');
|
||||
|
||||
?>
|
||||
<div class="app-empty-state" data-size="<?php e($emptySize); ?>" data-align="<?php e($emptyAlign); ?>">
|
||||
<p class="app-empty-state-message"><?php e($emptyMessage); ?></p>
|
||||
<?php if ($emptyHint !== ''): ?>
|
||||
<small class="app-empty-state-hint"><?php e($emptyHint); ?></small>
|
||||
<?php endif; ?>
|
||||
<?php if ($showAction): ?>
|
||||
<?php if ($emptyActionHref !== ''): ?>
|
||||
<a
|
||||
role="button"
|
||||
class="<?php e($emptyActionClass); ?> app-empty-state-action"
|
||||
href="<?php e($emptyActionHref); ?>"
|
||||
<?php if ($emptyActionTarget !== ''): ?>target="<?php e($emptyActionTarget); ?>"<?php endif; ?>
|
||||
<?php if ($emptyActionRel !== ''): ?>rel="<?php e($emptyActionRel); ?>"<?php endif; ?>
|
||||
>
|
||||
<?php e($emptyActionLabel); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<button type="submit" class="<?php e($emptyActionClass); ?> app-empty-state-action" form="<?php e($emptyActionForm); ?>">
|
||||
<?php e($emptyActionLabel); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -311,6 +311,10 @@
|
||||
--app-card-box-shadow: var(--app-box-shadow);
|
||||
--app-card-sectioning-background-color: rgb(251, 251.5, 252.25);
|
||||
--app-details-card-open-summary-bg: var(--app-card-sectioning-background-color);
|
||||
--app-empty-border-color: var(--app-muted-border-color);
|
||||
--app-empty-bg: var(--app-card-background-color);
|
||||
--app-empty-text: var(--app-color);
|
||||
--app-empty-hint: var(--app-muted-color);
|
||||
--app-dropdown-background-color: #fff;
|
||||
--app-dropdown-border-color: #eff1f4;
|
||||
--app-dropdown-box-shadow: var(--app-box-shadow);
|
||||
|
||||
44
web/css/components/app-empty-state.css
Normal file
44
web/css/components/app-empty-state.css
Normal file
@@ -0,0 +1,44 @@
|
||||
@layer components {
|
||||
.app-empty-state {
|
||||
border: 1px dashed var(--app-empty-border-color);
|
||||
border-radius: var(--app-border-radius);
|
||||
background: var(--app-empty-bg);
|
||||
color: var(--app-empty-text);
|
||||
padding: calc(var(--app-spacing) * 0.875) var(--app-spacing);
|
||||
display: grid;
|
||||
gap: calc(var(--app-spacing) * 0.35);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-empty-state[data-size="compact"] {
|
||||
padding: calc(var(--app-spacing) * 0.625) calc(var(--app-spacing) * 0.75);
|
||||
gap: calc(var(--app-spacing) * 0.25);
|
||||
}
|
||||
|
||||
.app-empty-state[data-align="left"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.app-empty-state-message {
|
||||
margin: 0;
|
||||
color: var(--app-empty-text);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.app-empty-state-hint {
|
||||
margin: 0;
|
||||
color: var(--app-empty-hint);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.app-empty-state-action {
|
||||
justify-self: center;
|
||||
margin-top: calc(var(--app-spacing) * 0.15);
|
||||
}
|
||||
|
||||
.app-empty-state[data-align="left"] .app-empty-state-action {
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user