Skip to content

Commit 473ecc3

Browse files
committed
fix: use Nova-style signal stepper in SM Lab
Fixes #109
1 parent 19616e5 commit 473ecc3

8 files changed

Lines changed: 169 additions & 80 deletions

File tree

dist/js/lab-showcase-rtl.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.

dist/js/lab-showcase.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.

dist/js/lab-showcase.js

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

src/Lab/ShowcaseRenderer.php

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -406,38 +406,55 @@ class="sm-lab-signal-cascade__node sm-lab-signal-cascade__node--<?php echo esc_a
406406
tabindex="0"
407407
style="<?php echo esc_attr( $node['style'] ); ?>"
408408
>
409-
<button
410-
type="button"
411-
class="sm-lab-signal-cascade__signal"
412-
data-sm-lab-cascade-signal-control
413-
aria-label="<?php echo esc_attr( sprintf( __( 'Change %1$s Color Signal. Current: %2$s', '__plugin_txtd' ), $node['label'], $node['signal_label'] ) ); ?>"
414-
>
415-
<span class="sm-lab-signal-bars__icon" data-sm-lab-cascade-signal-icon aria-hidden="true">
416-
<?php for ( $bar = 1; $bar <= 3; $bar++ ) : ?>
417-
<span class="<?php echo esc_attr( $bar <= $node['signal'] ? 'is-active' : '' ); ?>" data-sm-lab-cascade-signal-bar="<?php echo esc_attr( (string) $bar ); ?>"></span>
418-
<?php endfor; ?>
409+
<div class="sm-lab-signal-cascade__signal">
410+
<button
411+
type="button"
412+
class="sm-lab-signal-cascade__signal-summary"
413+
data-sm-lab-cascade-signal-control
414+
data-sm-lab-cascade-signal-summary
415+
data-sm-lab-cascade-signal-step="1"
416+
aria-label="<?php echo esc_attr( sprintf( __( 'Change %1$s Color Signal. Current: %2$s', '__plugin_txtd' ), $node['label'], $node['signal_label'] ) ); ?>"
417+
>
418+
<span class="sm-lab-signal-bars__icon" data-sm-lab-cascade-signal-icon aria-hidden="true">
419+
<?php for ( $bar = 1; $bar <= 3; $bar++ ) : ?>
420+
<span class="<?php echo esc_attr( $bar <= $node['signal'] ? 'is-active' : '' ); ?>" data-sm-lab-cascade-signal-bar="<?php echo esc_attr( (string) $bar ); ?>"></span>
421+
<?php endfor; ?>
422+
</span>
423+
<span class="sm-lab-signal-cascade__signal-copy">
424+
<span data-sm-lab-cascade-signal-kicker><?php esc_html_e( 'LEVEL', '__plugin_txtd' ); ?></span>
425+
<span data-sm-lab-cascade-value="signal"><?php echo esc_html( $node['signal_label'] ); ?></span>
426+
</span>
427+
</button>
428+
<span class="sm-lab-signal-cascade__signal-actions" aria-hidden="false">
429+
<button
430+
type="button"
431+
class="sm-lab-signal-cascade__signal-step"
432+
data-sm-lab-cascade-signal-control
433+
data-sm-lab-cascade-signal-step="-1"
434+
aria-label="<?php echo esc_attr( sprintf( __( 'Decrease %1$s Color Signal. Current: %2$s', '__plugin_txtd' ), $node['label'], $node['signal_label'] ) ); ?>"
435+
>−</button>
436+
<button
437+
type="button"
438+
class="sm-lab-signal-cascade__signal-step"
439+
data-sm-lab-cascade-signal-control
440+
data-sm-lab-cascade-signal-step="1"
441+
aria-label="<?php echo esc_attr( sprintf( __( 'Increase %1$s Color Signal. Current: %2$s', '__plugin_txtd' ), $node['label'], $node['signal_label'] ) ); ?>"
442+
>+</button>
419443
</span>
420-
<span data-sm-lab-cascade-value="signal"><?php echo esc_html( $node['signal_label'] ); ?></span>
421-
</button>
444+
</div>
422445
<strong><?php echo esc_html( $node['label'] ); ?></strong>
423-
<small>
424-
<span data-sm-lab-cascade-caption-signal><?php echo esc_html( sprintf( __( 'Color Signal: %s.', '__plugin_txtd' ), $node['signal_label'] ) ); ?></span>
425-
<?php echo esc_html( $node['caption'] ); ?>
426-
</small>
446+
<small><?php echo esc_html( $node['caption'] ); ?></small>
427447
<div class="sm-lab-cascade-chips" aria-hidden="true">
428-
<span class="sm-lab-cascade-chip" data-sm-lab-cascade-chip="signal-label" data-sm-lab-cascade-chip-signal-label>
429-
<?php esc_html_e( 'Color Signal:', '__plugin_txtd' ); ?> <?php echo esc_html( $node['signal_label'] ); ?>
430-
</span>
431448
<span
432449
class="sm-lab-cascade-chip"
433450
data-sm-lab-cascade-chip="saved-attribute"
434451
data-sm-lab-cascade-chip-signal-input="<?php echo esc_attr( (string) $node['signal'] ); ?>"
435452
>
436-
<span><?php esc_html_e( 'Saved attribute:', '__plugin_txtd' ); ?></span>
453+
<span><?php esc_html_e( 'Saved:', '__plugin_txtd' ); ?></span>
437454
<code data-sm-lab-cascade-chip-signal-input-code>data-color-signal="<?php echo esc_html( (string) $node['signal'] ); ?>"</code>
438455
</span>
439456
<span class="sm-lab-cascade-chip" data-sm-lab-cascade-chip="scope-class">
440-
<span><?php esc_html_e( 'Resolved scope:', '__plugin_txtd' ); ?></span>
457+
<span><?php esc_html_e( 'Scope:', '__plugin_txtd' ); ?></span>
441458
<code data-sm-lab-cascade-chip-scope>.sm-palette-<?php echo esc_html( $params->palette() ); ?>.sm-variation-<?php echo esc_html( (string) $node['resolved_grade'] ); ?></code>
442459
</span>
443460
</div>

src/_js/lab-showcase/runtime.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -917,26 +917,22 @@ const setSignalCascadeValue = ( node, key, value ) => {
917917

918918
const updateCascadeSignalUi = ( node, signal ) => {
919919
const label = SIGNAL_LABELS[ signal ] || SIGNAL_LABELS[0];
920-
const control = node.querySelector( '[data-sm-lab-cascade-signal-control]' );
921920
const title = node.querySelector( 'strong' )?.textContent?.trim() || 'block';
922921

923-
if ( control ) {
924-
control.setAttribute( 'aria-label', `Change ${ title } Color Signal. Current: ${ label }` );
925-
}
922+
node.querySelectorAll( '[data-sm-lab-cascade-signal-control]' ).forEach( ( control ) => {
923+
const step = Number( control.getAttribute( 'data-sm-lab-cascade-signal-step' ) || 1 );
924+
const action = control.getAttribute( 'data-sm-lab-cascade-signal-summary' ) !== null
925+
? 'Change'
926+
: ( step < 0 ? 'Decrease' : 'Increase' );
927+
928+
control.setAttribute( 'aria-label', `${ action } ${ title } Color Signal. Current: ${ label }` );
929+
} );
926930

927931
node.querySelectorAll( '[data-sm-lab-cascade-signal-bar]' ).forEach( ( bar ) => {
928932
const barValue = Number( bar.getAttribute( 'data-sm-lab-cascade-signal-bar' ) );
929933
bar.classList.toggle( 'is-active', barValue <= signal );
930934
} );
931935

932-
node.querySelectorAll( '[data-sm-lab-cascade-chip-signal-label]' ).forEach( ( chip ) => {
933-
chip.textContent = `Color Signal: ${ label }`;
934-
} );
935-
936-
node.querySelectorAll( '[data-sm-lab-cascade-caption-signal]' ).forEach( ( caption ) => {
937-
caption.textContent = `Color Signal: ${ label }.`;
938-
} );
939-
940936
node.querySelectorAll( '[data-sm-lab-cascade-chip-signal-input]' ).forEach( ( chip ) => {
941937
chip.setAttribute( 'data-sm-lab-cascade-chip-signal-input', String( signal ) );
942938
} );
@@ -988,7 +984,10 @@ const wireCascadeInteractions = ( cascade, documentRef ) => {
988984
return;
989985
}
990986

991-
const nextSignal = ( normalizeSignalValue( node.getAttribute( 'data-sm-lab-cascade-signal' ) ) + 1 ) % SIGNAL_LABELS.length;
987+
const step = Number( control.getAttribute( 'data-sm-lab-cascade-signal-step' ) || 1 );
988+
const normalizedStep = Number.isNaN( step ) ? 1 : step;
989+
const currentSignal = normalizeSignalValue( node.getAttribute( 'data-sm-lab-cascade-signal' ) );
990+
const nextSignal = ( currentSignal + normalizedStep + SIGNAL_LABELS.length ) % SIGNAL_LABELS.length;
992991
node.setAttribute( 'data-sm-lab-cascade-signal', String( nextSignal ) );
993992
updateSignalCascade( documentRef, state, cascade.__smLabCascadeWindowRef || {} );
994993
} );

src/_js/lab/showcase.scss

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -631,38 +631,88 @@
631631
}
632632

633633
.sm-lab-signal-cascade__signal {
634-
display: flex;
635-
gap: 7px;
636-
align-items: end;
634+
display: inline-flex;
635+
gap: 6px;
636+
align-items: stretch;
637637
justify-self: start;
638-
padding: 0;
639-
border: 0;
640-
background: transparent;
638+
color: currentColor;
639+
font-size: 11px;
640+
}
641+
642+
.sm-lab-signal-cascade__signal-summary,
643+
.sm-lab-signal-cascade__signal-step {
644+
border: 1px solid color-mix(in srgb, currentColor, transparent 70%);
645+
border-radius: 4px;
646+
background: color-mix(in srgb, currentColor, transparent 94%);
641647
color: currentColor;
642648
cursor: pointer;
649+
}
650+
651+
.sm-lab-signal-cascade__signal-summary {
652+
display: grid;
653+
grid-template-columns: 18px minmax(0, auto);
654+
gap: 6px;
655+
align-items: end;
656+
min-height: 34px;
657+
padding: 5px 7px;
658+
text-align: left;
659+
}
660+
661+
.sm-lab-signal-cascade__signal-copy {
662+
display: grid;
663+
gap: 1px;
664+
line-height: 1.05;
665+
}
666+
667+
.sm-lab-signal-cascade__signal-copy [data-sm-lab-cascade-signal-kicker] {
668+
color: color-mix(in srgb, currentColor, transparent 34%);
669+
font-size: 9px;
670+
font-weight: 700;
671+
text-transform: uppercase;
672+
}
673+
674+
.sm-lab-signal-cascade__signal-copy [data-sm-lab-cascade-value="signal"] {
643675
font-size: 11px;
644676
font-weight: 800;
677+
}
678+
679+
.sm-lab-signal-cascade__signal-actions {
680+
display: inline-flex;
681+
gap: 4px;
682+
align-items: stretch;
683+
}
684+
685+
.sm-lab-signal-cascade__signal-step {
686+
display: grid;
687+
place-items: center;
688+
min-width: 28px;
689+
min-height: 34px;
690+
padding: 0 6px;
691+
font-size: 16px;
692+
font-weight: 700;
645693
line-height: 1;
646-
text-transform: uppercase;
647694
}
648695

649-
.sm-lab-signal-cascade__signal:is(:hover, :focus-visible) {
696+
.sm-lab-signal-cascade__signal-summary:is(:hover, :focus-visible),
697+
.sm-lab-signal-cascade__signal-step:is(:hover, :focus-visible) {
698+
border-color: color-mix(in srgb, currentColor, transparent 42%);
699+
background: color-mix(in srgb, currentColor, transparent 88%);
650700
color: color-mix(in srgb, currentColor, var(--sm-current-accent-color) 28%);
651701
outline: 2px solid color-mix(in srgb, var(--sm-current-accent-color), transparent 45%);
652-
outline-offset: 3px;
702+
outline-offset: 2px;
653703
}
654704

655-
.sm-lab-signal-cascade__signal .sm-lab-signal-bars__icon {
705+
.sm-lab-signal-cascade__signal-summary .sm-lab-signal-bars__icon {
656706
grid-template-columns: repeat(3, 3px);
657707
width: auto;
658708
height: 14px;
659709
}
660710

661-
.sm-lab-signal-cascade__signal .sm-lab-signal-bars__icon span {
711+
.sm-lab-signal-cascade__signal-summary .sm-lab-signal-bars__icon span {
662712
background: color-mix(in srgb, currentColor, transparent 74%);
663713
}
664714

665-
.sm-lab-signal-cascade__signal .sm-lab-signal-bars__icon .is-active {
715+
.sm-lab-signal-cascade__signal-summary .sm-lab-signal-bars__icon .is-active {
666716
background: currentColor;
667717
}
668718

tests/js/lab-showcase-runtime.test.js

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ const createShowcaseDocument = () => {
381381

382382
const signalControl = documentRef.createElement( 'button' );
383383
signalControl.setAttribute( 'data-sm-lab-cascade-signal-control', '1' );
384+
signalControl.setAttribute( 'data-sm-lab-cascade-signal-summary', '1' );
385+
signalControl.setAttribute( 'data-sm-lab-cascade-signal-step', '1' );
384386
const signalIcon = documentRef.createElement( 'span' );
385387
signalIcon.setAttribute( 'data-sm-lab-cascade-signal-icon', '1' );
386388
signalControl.appendChild( signalIcon );
@@ -391,22 +393,24 @@ const createShowcaseDocument = () => {
391393
signalIcon.appendChild( barNode );
392394
}
393395

396+
const kicker = documentRef.createElement( 'span' );
397+
kicker.setAttribute( 'data-sm-lab-cascade-signal-kicker', '1' );
398+
kicker.textContent = 'LEVEL';
399+
signalControl.appendChild( kicker );
394400
signalControl.appendChild( createCascadeValue( documentRef, 'signal' ) );
395401
node.appendChild( signalControl );
396402

403+
[ '-1', '1' ].forEach( ( step ) => {
404+
const stepControl = documentRef.createElement( 'button' );
405+
stepControl.setAttribute( 'data-sm-lab-cascade-signal-control', '1' );
406+
stepControl.setAttribute( 'data-sm-lab-cascade-signal-step', step );
407+
node.appendChild( stepControl );
408+
} );
409+
397410
[ 'parent', 'resolved' ].forEach( ( key ) => {
398411
node.appendChild( createCascadeValue( documentRef, key ) );
399412
} );
400413

401-
const captionSignal = documentRef.createElement( 'small' );
402-
captionSignal.setAttribute( 'data-sm-lab-cascade-caption-signal', '1' );
403-
node.appendChild( captionSignal );
404-
405-
const signalLabelChip = documentRef.createElement( 'span' );
406-
signalLabelChip.setAttribute( 'data-sm-lab-cascade-chip', 'signal-label' );
407-
signalLabelChip.setAttribute( 'data-sm-lab-cascade-chip-signal-label', '1' );
408-
node.appendChild( signalLabelChip );
409-
410414
const savedAttributeChip = documentRef.createElement( 'span' );
411415
savedAttributeChip.setAttribute( 'data-sm-lab-cascade-chip', 'saved-attribute' );
412416
savedAttributeChip.setAttribute( 'data-sm-lab-cascade-chip-signal-input', String( signal ) );
@@ -736,14 +740,19 @@ export const runLabShowcaseRuntimeTests = async ( assert ) => {
736740
'signal cascade should label Low signal nodes'
737741
);
738742
assert.equal(
739-
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-chip-signal-label]' )?.textContent,
740-
'Color Signal: Low',
741-
'signal cascade should expose the human Color Signal label separately from the saved value'
743+
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-signal-kicker]' )?.textContent,
744+
'LEVEL',
745+
'signal cascade should use the compact Nova-style level label'
746+
);
747+
assert.equal(
748+
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-chip-signal-label]' ),
749+
null,
750+
'signal cascade should not repeat the human Color Signal as a chip'
742751
);
743752
assert.equal(
744-
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-caption-signal]' )?.textContent,
745-
'Color Signal: Low.',
746-
'signal cascade captions should expose the current human Color Signal label'
753+
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-caption-signal]' ),
754+
null,
755+
'signal cascade should not repeat the human Color Signal in the caption'
747756
);
748757
assert.equal(
749758
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-chip-signal-input-code]' )?.textContent,
@@ -839,7 +848,7 @@ export const runLabShowcaseRuntimeTests = async ( assert ) => {
839848
1,
840849
'cascade hover bond should bind pointer listeners once'
841850
);
842-
const contentSignalControl = documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-signal-control]' );
851+
const contentSignalControl = documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-signal-step="1"]' );
843852
cascade.listeners.get( 'click' )?.[0]?.( {
844853
target: contentSignalControl,
845854
preventDefault: () => {},
@@ -854,16 +863,6 @@ export const runLabShowcaseRuntimeTests = async ( assert ) => {
854863
'Medium',
855864
'clicking a cascade signal control should update the visible human signal name'
856865
);
857-
assert.equal(
858-
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-chip-signal-label]' )?.textContent,
859-
'Color Signal: Medium',
860-
'clicking a cascade signal control should update the human signal chip'
861-
);
862-
assert.equal(
863-
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-caption-signal]' )?.textContent,
864-
'Color Signal: Medium.',
865-
'clicking a cascade signal control should update the human signal caption'
866-
);
867866
assert.equal(
868867
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-chip-signal-input-code]' )?.textContent,
869868
'data-color-signal="2"',
@@ -879,6 +878,26 @@ export const runLabShowcaseRuntimeTests = async ( assert ) => {
879878
'1',
880879
'clicking a parent cascade signal control should recompute descendants from the new parent grade'
881880
);
881+
const contentSignalDecreaseControl = documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-signal-step="-1"]' );
882+
cascade.listeners.get( 'click' )?.[0]?.( {
883+
target: contentSignalDecreaseControl,
884+
preventDefault: () => {},
885+
} );
886+
assert.equal(
887+
documentRef.querySelector( '[data-sm-lab-cascade-node="content"]' )?.getAttribute( 'data-sm-lab-cascade-signal' ),
888+
'1',
889+
'clicking the negative cascade signal control should cycle that node backward'
890+
);
891+
assert.equal(
892+
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-value="signal"]' )?.textContent,
893+
'Low',
894+
'clicking the negative cascade signal control should update the visible human signal name'
895+
);
896+
assert.equal(
897+
documentRef.querySelector( '[data-sm-lab-cascade-node="content"] [data-sm-lab-cascade-chip-signal-input-code]' )?.textContent,
898+
'data-color-signal="1"',
899+
'clicking the negative cascade signal control should update the saved attribute chip'
900+
);
882901
assert.equal(
883902
documentRef.querySelector( '[data-sm-lab-signal-preview]' )?.getAttribute( 'data-palette' ),
884903
'contextual-lab',

tests/phpunit/Unit/Lab/ShowcaseRendererTest.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ public function test_render_outputs_unique_value_showcase_sections(): void {
6363
$this->assertStringContainsString( 'data-sm-lab-cascade-signal="3"', $html );
6464
$this->assertStringContainsString( 'tabindex="0"', $html );
6565
$this->assertStringContainsString( 'data-sm-lab-cascade-signal-control', $html );
66+
$this->assertStringContainsString( 'data-sm-lab-cascade-signal-step="1"', $html );
67+
$this->assertStringContainsString( 'data-sm-lab-cascade-signal-step="-1"', $html );
6668
$this->assertStringContainsString( 'type="button"', $html );
6769
$this->assertStringContainsString( 'Change Page container Color Signal. Current: None', $html );
68-
$this->assertStringContainsString( 'data-sm-lab-cascade-caption-signal', $html );
70+
$this->assertStringContainsString( 'LEVEL', $html );
71+
$this->assertStringNotContainsString( 'data-sm-lab-cascade-caption-signal', $html );
6972
$this->assertStringContainsString( 'data-sm-lab-cascade-active="true"', $html );
7073
$this->assertStringContainsString( 'data-sm-lab-cascade-value="parent"', $html );
7174
$this->assertStringContainsString( '--sm-lab-cascade-surface-color', $html );
@@ -82,14 +85,15 @@ public function test_render_outputs_unique_value_showcase_sections(): void {
8285
$this->assertStringContainsString( 'data-sm-lab-cascade-resolved-grade="6"', $html );
8386
$this->assertStringContainsString( 'data-sm-lab-cascade-resolved-grade="10"', $html );
8487
$this->assertStringNotContainsString( 'sm-lab-signal-cascade__wires', $html );
85-
$this->assertStringContainsString( 'data-sm-lab-cascade-chip="signal-label"', $html );
88+
$this->assertStringNotContainsString( 'data-sm-lab-cascade-chip="signal-label"', $html );
8689
$this->assertStringContainsString( 'data-sm-lab-cascade-chip="saved-attribute"', $html );
8790
$this->assertStringContainsString( 'data-sm-lab-cascade-chip="scope-class"', $html );
8891
$this->assertStringContainsString( 'data-sm-lab-cascade-chip-signal-input="3"', $html );
8992
$this->assertStringContainsString( 'data-sm-lab-cascade-chip-signal-input="0"', $html );
90-
$this->assertStringContainsString( 'Color Signal: High', $html );
91-
$this->assertStringContainsString( 'Color Signal: None', $html );
92-
$this->assertStringContainsString( 'Saved attribute', $html );
93+
$this->assertStringNotContainsString( 'Color Signal: High', $html );
94+
$this->assertStringNotContainsString( 'Color Signal: None', $html );
95+
$this->assertStringContainsString( 'Saved:', $html );
96+
$this->assertStringContainsString( 'Scope:', $html );
9397
$this->assertStringContainsString( 'data-sm-lab-cascade-rail', $html );
9498
$this->assertStringContainsString( 'data-sm-lab-cascade-rail-grade="1"', $html );
9599
$this->assertStringContainsString( 'data-sm-lab-cascade-rail-grade="12"', $html );

0 commit comments

Comments
 (0)