Skip to content

Commit ce2c014

Browse files
committed
feat(user): present the User panel with a hero() header, marked attribute sections, and counted RBAC sections; match textual statuses by label and word the guest state for any framework.
1 parent f0f3f5a commit ce2c014

13 files changed

Lines changed: 669 additions & 182 deletions

File tree

‎resources/assets/dist/css/debug.min.css‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎resources/src/styles/main.css‎

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@
10011001
*/
10021002
.yii-debug-request-hero-method,
10031003
.yii-debug-snapshot-status,
1004+
.yii-debug-hero-status > .yii-debug-badge,
10041005
.yii-debug-level-chip,
10051006
.yii-debug-db-type,
10061007
.yii-debug-dump-type,
@@ -3742,6 +3743,162 @@
37423743
white-space: nowrap;
37433744
}
37443745

3746+
/**
3747+
* ===================================================================
3748+
* Hero — the header of a panel about one subject.
3749+
* ===================================================================
3750+
* The Request overview rail, generalized: an accent rail, a mark tile,
3751+
* the title and subtitle, a status chip, and one metric row. The status
3752+
* tone sets `--yii-debug-hue`, so the rail, the mark, and the chip share
3753+
* one hue; the muted variant keeps the neutral fallback.
3754+
*/
3755+
.yii-debug-hero {
3756+
overflow: hidden;
3757+
margin-bottom: var(--yii-debug-space-5);
3758+
border: var(--yii-debug-border);
3759+
border-inline-start: 4px solid
3760+
color-mix(
3761+
in srgb,
3762+
var(--yii-debug-hue, var(--yii-debug-panel-muted)) 76%,
3763+
var(--yii-debug-panel-text)
3764+
);
3765+
border-radius: var(--yii-debug-radius-lg);
3766+
background: var(--yii-debug-panel-surface);
3767+
box-shadow: var(--yii-debug-shadow-sm);
3768+
}
3769+
3770+
.yii-debug-hero-danger {
3771+
--yii-debug-hue: var(--yii-debug-danger);
3772+
}
3773+
3774+
.yii-debug-hero-info {
3775+
--yii-debug-hue: var(--yii-debug-info);
3776+
}
3777+
3778+
.yii-debug-hero-success {
3779+
--yii-debug-hue: var(--yii-debug-success);
3780+
}
3781+
3782+
.yii-debug-hero-warning {
3783+
--yii-debug-hue: var(--yii-debug-warning);
3784+
}
3785+
3786+
.yii-debug-hero-header {
3787+
display: grid;
3788+
grid-template-columns: minmax(0, 1fr) auto;
3789+
gap: var(--yii-debug-space-4);
3790+
align-items: center;
3791+
padding: var(--yii-debug-space-4);
3792+
}
3793+
3794+
.yii-debug-hero-identity {
3795+
display: flex;
3796+
min-width: 0;
3797+
align-items: center;
3798+
gap: var(--yii-debug-space-3);
3799+
}
3800+
3801+
.yii-debug-hero-mark {
3802+
display: inline-flex;
3803+
width: 40px;
3804+
height: 40px;
3805+
flex: none;
3806+
align-items: center;
3807+
justify-content: center;
3808+
border: 1px solid
3809+
color-mix(
3810+
in srgb,
3811+
var(--yii-debug-hue, var(--yii-debug-panel-muted)),
3812+
transparent 65%
3813+
);
3814+
border-radius: var(--yii-debug-radius-md);
3815+
background: color-mix(
3816+
in srgb,
3817+
var(--yii-debug-hue, var(--yii-debug-panel-muted)),
3818+
transparent 88%
3819+
);
3820+
color: color-mix(
3821+
in srgb,
3822+
var(--yii-debug-hue, var(--yii-debug-panel-muted)) 80%,
3823+
var(--yii-debug-panel-text)
3824+
);
3825+
font-family: var(--yii-debug-font-mono);
3826+
font-size: var(--yii-debug-fs-sm);
3827+
font-weight: 700;
3828+
letter-spacing: 0.04em;
3829+
text-transform: uppercase;
3830+
}
3831+
3832+
.yii-debug-hero-text {
3833+
display: flex;
3834+
min-width: 0;
3835+
flex-direction: column;
3836+
gap: 2px;
3837+
}
3838+
3839+
.yii-debug-hero-title,
3840+
.yii-debug-hero-subtitle {
3841+
overflow: hidden;
3842+
font-family: var(--yii-debug-font-mono);
3843+
text-overflow: ellipsis;
3844+
white-space: nowrap;
3845+
}
3846+
3847+
.yii-debug-hero-title {
3848+
color: var(--yii-debug-panel-text);
3849+
font-size: var(--yii-debug-fs-base);
3850+
font-weight: 600;
3851+
}
3852+
3853+
.yii-debug-hero-subtitle {
3854+
color: var(--yii-debug-panel-muted);
3855+
font-size: var(--yii-debug-fs-xs);
3856+
}
3857+
3858+
.yii-debug-hero-status > .yii-debug-badge {
3859+
padding: 3px 12px;
3860+
font-family: var(--yii-debug-font-mono);
3861+
font-size: var(--yii-debug-fs-sm);
3862+
font-weight: 700;
3863+
letter-spacing: 0.02em;
3864+
}
3865+
3866+
.yii-debug-hero-metrics {
3867+
display: grid;
3868+
margin: 0;
3869+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
3870+
border-top: var(--yii-debug-border);
3871+
background: var(--yii-debug-panel-border);
3872+
gap: 1px;
3873+
}
3874+
3875+
.yii-debug-hero-metric {
3876+
min-width: 0;
3877+
padding: var(--yii-debug-space-3) var(--yii-debug-space-4);
3878+
background: var(--yii-debug-panel-surface-muted);
3879+
3880+
dt {
3881+
margin-bottom: 2px;
3882+
color: var(--yii-debug-panel-muted);
3883+
font-family: var(--yii-debug-font-sans);
3884+
font-size: 0.6875rem;
3885+
font-weight: 700;
3886+
letter-spacing: 0.07em;
3887+
text-transform: uppercase;
3888+
}
3889+
3890+
dd {
3891+
overflow: hidden;
3892+
margin: 0;
3893+
color: var(--yii-debug-panel-text);
3894+
font-family: var(--yii-debug-font-mono);
3895+
font-size: var(--yii-debug-fs-sm);
3896+
font-weight: 600;
3897+
text-overflow: ellipsis;
3898+
white-space: nowrap;
3899+
}
3900+
}
3901+
37453902
/**
37463903
* ===================================================================
37473904
* Entity inventory — stat tiles, entity cards, file lists, and link

‎src/Panel/PanelRenderer.php‎

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace PHPForge\Debug\Panel;
66

77
use JsonException;
8-
use PHPForge\Debug\{ColumnStyle, PanelView};
8+
use PHPForge\Debug\{ColumnStyle, PanelView, Tone};
99
use PHPForge\Debug\Helper\{Badge, CellMore, Disclosure, EmptyState, ExtensionPill, Format, Icon, Table, Trace};
1010
use PHPForge\Debug\Panel\Db\SqlHighlighter;
1111
use PHPForge\Debug\Presenter\{
@@ -19,6 +19,7 @@
1919
FilesBlock,
2020
GroupBlock,
2121
HeadingBlock,
22+
HeroBlock,
2223
Inline,
2324
LinkInline,
2425
LinksBlock,
@@ -161,6 +162,7 @@ private function block(Block $block): string
161162
: H2::tag()
162163
->content($block->title)
163164
->render(),
165+
$block instanceof HeroBlock => self::hero($block),
164166
$block instanceof LinksBlock => $this->links($block),
165167
$block instanceof ManifestBlock => self::manifest($block),
166168
$block instanceof OverviewBlock => $this->overview($block),
@@ -387,6 +389,88 @@ private function frames(TraceInline $inline): string
387389
->render();
388390
}
389391

392+
/**
393+
* Renders the header identifying the subject of the panel, in the look of the Request overview.
394+
*
395+
* The status tone colors the accent rail, the mark, and the status chip; a header without a status stays neutral.
396+
*
397+
* @param HeroBlock $block Hero block to render.
398+
*
399+
* @return string Hero markup.
400+
*/
401+
private static function hero(HeroBlock $block): string
402+
{
403+
$identity = [];
404+
405+
if ($block->mark !== '') {
406+
$identity[] = Span::tag()
407+
->addAriaAttribute('hidden', 'true')
408+
->class(Css::HERO_MARK)
409+
->content($block->mark);
410+
}
411+
412+
$text = [
413+
Span::tag()
414+
->class(Css::HERO_TITLE)
415+
->title($block->title)
416+
->content($block->title),
417+
];
418+
419+
if ($block->subtitle !== '') {
420+
$text[] = Span::tag()
421+
->class(Css::HERO_SUBTITLE)
422+
->title($block->subtitle)
423+
->content($block->subtitle);
424+
}
425+
426+
$identity[] = Div::tag()
427+
->class(Css::HERO_TEXT)
428+
->html(...$text);
429+
430+
$header = [
431+
Div::tag()
432+
->class(Css::HERO_IDENTITY)
433+
->html(...$identity),
434+
];
435+
436+
if ($block->status !== null) {
437+
$header[] = Div::tag()
438+
->class(Css::HERO_STATUS)
439+
->html(Badge::render($block->status->label, $block->status->tone));
440+
}
441+
442+
$parts = [
443+
Header::tag()
444+
->class(Css::HERO_HEADER)
445+
->html(...$header),
446+
];
447+
448+
if ($block->metrics !== []) {
449+
$metrics = [];
450+
451+
foreach ($block->metrics as $metric) {
452+
$metrics[] = Div::tag()
453+
->class(Css::HERO_METRIC)
454+
->html(
455+
Dt::tag()->content($metric->label),
456+
Dd::tag()
457+
->title($metric->value)
458+
->content($metric->value),
459+
);
460+
}
461+
462+
$parts[] = Dl::tag()
463+
->class(Css::HERO_METRICS)
464+
->html(...$metrics);
465+
}
466+
467+
return Section::tag()
468+
->addAriaAttribute('label', $block->title)
469+
->class(Css::hero($block->status->tone ?? Tone::MUTED))
470+
->html(...$parts)
471+
->render();
472+
}
473+
390474
/**
391475
* Renders one inline value through the renderer its type selects.
392476
*

‎src/Panel/User/UserDataNormalizer.php‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use function str_ends_with;
1515
use function str_replace;
1616
use function str_starts_with;
17+
use function strcasecmp;
1718
use function strlen;
1819
use function substr;
1920
use function trim;
@@ -193,10 +194,10 @@ private static function buildSections(array $identity, array $labels): array
193194
{
194195
$buckets = ['identity' => [], 'security' => [], 'timestamps' => [], 'other' => []];
195196

196-
foreach (['id', 'username', 'name', 'email'] as $key) {
197-
if (isset($identity[$key])) {
198-
$buckets['identity'][$key] = $identity[$key];
199-
}
197+
// The hero already shows the identifier, the email, and the name it uses as its title, so only a second name
198+
// is left for the Identity section.
199+
if (isset($identity['username'], $identity['name'])) {
200+
$buckets['identity']['name'] = $identity['name'];
200201
}
201202

202203
foreach ($identity as $key => $value) {
@@ -311,6 +312,9 @@ private static function labelFor(string $key, array $labels): string
311312
/**
312313
* Resolves the status display label and CSS variant for the raw status value.
313314
*
315+
* The Yii2 status codes resolve first; a textual status such as the name of a Yii3 status enum resolves by its
316+
* label, case-insensitively.
317+
*
314318
* @return array{0: string, 1: string} `[label, variant]`.
315319
*/
316320
private static function resolveStatus(string $value): array
@@ -321,6 +325,12 @@ private static function resolveStatus(string $value): array
321325
return [self::STATUS_MAP[$raw]['label'], self::STATUS_MAP[$raw]['variant']];
322326
}
323327

328+
foreach (self::STATUS_MAP as $status) {
329+
if (strcasecmp($status['label'], $raw) === 0) {
330+
return [$status['label'], $status['variant']];
331+
}
332+
}
333+
324334
return [$raw === '' ? 'Unknown' : $raw, 'muted'];
325335
}
326336

‎src/Panel/User/UserMessage.php‎

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,19 @@ enum UserMessage: string
4545
case EMPTY_HEADLINE = 'No authenticated user';
4646

4747
/**
48-
* Identity accessor named in the empty state, whose resolution populates the panel.
48+
* Call to action of the empty state, worded for any framework.
4949
*/
50-
case EMPTY_IDENTITY = 'Yii::$app->user->identity';
50+
case EMPTY_SIGN_IN = 'Sign in and reload the page; the identity appears here once the application resolves it.';
5151

5252
/**
53-
* Closing sentence of the empty-state call to action.
54-
*/
55-
case EMPTY_RESOLVES = ' resolves.';
56-
57-
/**
58-
* Call to action of the empty state, preceding the identity accessor.
53+
* Stable identifier associating the panel with the captured payload, also used as its icon key.
5954
*/
60-
case EMPTY_SIGN_IN = 'Sign in and reload the page; the identity appears here as soon as ';
55+
case ID = 'user';
6156

6257
/**
63-
* Stable identifier associating the panel with the captured payload, also used as its icon key.
58+
* Glyph marking the attribute and RBAC sections.
6459
*/
65-
case ID = 'user';
60+
case MARK = '::';
6661

6762
/**
6863
* Header of the RBAC item name column.
@@ -89,11 +84,6 @@ enum UserMessage: string
8984
*/
9085
case RBAC_EMPTY = 'The auth manager granted no %s to this identity.';
9186

92-
/**
93-
* `sprintf()` template of an RBAC section heading, naming the section and its item count.
94-
*/
95-
case RBAC_HEADING = '%s (%d)';
96-
9787
/**
9888
* Section label of the granted roles.
9989
*/

0 commit comments

Comments
 (0)