Skip to content

Commit 0b8f39b

Browse files
feat(newspack-ui): standardize snackbars + accessible notices (#445)
* feat(newspack-ui): standardize snackbars + accessible notices Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(newspack-ui): add ghost-dark button variant Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(newspack-ui): rename modal-width vars to width tokens Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(newspack-ui): refine snackbar layout, motion and error icon Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(newspack-ui): snackbar type cues and review fixes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(newspack-ui): address Copilot review feedback Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(newspack-ui): add elevation 2 to snackbar items Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8c09d8b commit 0b8f39b

21 files changed

Lines changed: 307 additions & 215 deletions

File tree

plugins/newspack-newsletters/includes/class-newspack-newsletters-subscription.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,6 @@ private static function add_account_notice( $message, $type = 'success' ) {
13561356
$message,
13571357
[
13581358
'type' => $type,
1359-
'corner' => 'top-right',
13601359
'autohide' => true,
13611360
'active_on_load' => true,
13621361
]

plugins/newspack-plugin/includes/class-newspack-ui-icons.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ public static function sanitize_svgs() {
9595
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false" class="newspack-ui__svg-icon--arrow-right">
9696
<path d="m14.5 6.5-1 1 3.7 3.7H4v1.6h13.2l-3.7 3.7 1 1 5.6-5.5z" />
9797
</svg>',
98+
'caution' =>
99+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false" class="newspack-ui__svg-icon--caution">
100+
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 12a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 12v-1.5h1.5V16h-1.5Zm0-8v5h1.5V8h-1.5Z" />
101+
</svg>',
98102
'check' =>
99103
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false" class="newspack-ui__svg-icon--check">
100104
<path d="M16.5 7.5 10 13.9l-2.5-2.4-1 1 3.5 3.6 7.5-7.6z" />

plugins/newspack-plugin/includes/class-newspack-ui.php

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Newspack_UI {
2020
*
2121
* @var array
2222
*/
23-
private static $notices = [];
23+
private static array $notices = [];
2424

2525
/**
2626
* Initialize hooks.
@@ -59,34 +59,56 @@ public static function enqueue_assets() {
5959
Newspack::asset_version( 'newspack-ui' ),
6060
true
6161
);
62+
63+
$icons = [];
64+
foreach ( self::get_type_icons() as $type => $icon_name ) {
65+
$icons[ $type ] = wp_kses( Newspack_UI_Icons::get_svg( $icon_name ), Newspack_UI_Icons::sanitize_svgs() );
66+
}
67+
wp_localize_script(
68+
'newspack-ui',
69+
'newspackUIData',
70+
[
71+
'icons' => $icons,
72+
]
73+
);
74+
}
75+
76+
/**
77+
* Map of notice types to their Newspack UI icon name. Types absent from the
78+
* map render no icon.
79+
*
80+
* @return array<string, string> Notice type => icon name.
81+
*/
82+
private static function get_type_icons() {
83+
return [
84+
'error' => 'error',
85+
'warning' => 'caution',
86+
];
6287
}
6388

6489
/**
6590
* Add a snackbar notice.
6691
*
6792
* @param string $message The notice message.
68-
* @param string|array $args Notice arguments array or notice type.
93+
* @param string|array $args Notice arguments array, or a notice type string as shorthand.
6994
*
7095
* @return string The notice ID.
7196
*/
72-
public static function add_notice( $message, $args = [] ) {
97+
public static function add_notice( string $message, string|array $args = [] ): string {
7398
if ( is_string( $args ) ) {
74-
$args = [
75-
'type' => $args,
76-
];
99+
$args = [ 'type' => $args ];
77100
}
78101
$notice = wp_parse_args(
79102
$args,
80103
[
81104
'message' => $message,
82-
'corner' => 'top-right',
83-
'type' => 'success',
105+
'type' => 'success', // Severity; drives the ARIA announcement politeness ('error' assertive, else polite), the type icon, and the [data-type] styling.
84106
'id' => uniqid(),
85107
'autohide' => true, // If false, the notice will have a close button.
86108
'active_on_load' => true, // Whether the notice should be visible on page load.
87109
]
88110
);
89-
self::$notices[ $notice['corner'] ][ $notice['id'] ] = $notice;
111+
self::$notices[ $notice['id'] ] = $notice;
90112

91113
return $notice['id'];
92114
}
@@ -98,36 +120,35 @@ public static function print_notices() {
98120
if ( empty( self::$notices ) ) {
99121
return;
100122
}
101-
102-
foreach ( self::$notices as $corner => $notices ) {
103-
if ( empty( $notices ) ) {
104-
continue;
105-
}
106-
?>
107-
<div class="newspack-ui">
108-
<div class="newspack-ui__snackbar newspack-ui__snackbar--<?php echo esc_attr( $corner ); ?>">
109-
<?php foreach ( $notices as $notice ) : ?>
110-
<div
111-
class="newspack-ui__snackbar__item newspack-ui__snackbar__item--<?php echo esc_attr( $notice['type'] ); ?>"
112-
data-notice-id="<?php echo esc_attr( $notice['id'] ); ?>"
113-
data-nonce="<?php echo esc_attr( wp_create_nonce( 'newspack_ui_notice_dismissed' ) ); ?>"
114-
data-autohide="<?php echo $notice['autohide'] ? 'true' : 'false'; ?>"
115-
data-active-on-load="<?php echo $notice['active_on_load'] ? 'true' : 'false'; ?>"
116-
>
117-
<?php if ( ! $notice['autohide'] ) : ?>
118-
<button class="newspack-ui__snackbar__close" aria-label="<?php esc_attr_e( 'Close', 'newspack-plugin' ); ?>" title="<?php esc_attr_e( 'Close', 'newspack-plugin' ); ?>">
119-
<?php Newspack_UI_Icons::print_svg( 'closeSmall' ); ?>
120-
</button>
121-
<?php endif; ?>
122-
<div class="newspack-ui__snackbar__content">
123-
<?php echo wp_kses_post( $notice['message'] ); ?>
124-
</div>
123+
?>
124+
<div class="newspack-ui">
125+
<div class="newspack-ui__snackbar">
126+
<?php foreach ( self::$notices as $notice ) : ?>
127+
<div
128+
class="newspack-ui__snackbar__item"
129+
data-type="<?php echo esc_attr( $notice['type'] ); ?>"
130+
data-notice-id="<?php echo esc_attr( $notice['id'] ); ?>"
131+
data-nonce="<?php echo esc_attr( wp_create_nonce( 'newspack_ui_notice_dismissed' ) ); ?>"
132+
data-autohide="<?php echo $notice['autohide'] ? 'true' : 'false'; ?>"
133+
data-active-on-load="<?php echo $notice['active_on_load'] ? 'true' : 'false'; ?>"
134+
>
135+
<?php $type_icons = self::get_type_icons(); ?>
136+
<?php if ( isset( $type_icons[ $notice['type'] ] ) ) : ?>
137+
<span class="newspack-ui__snackbar__icon"><?php Newspack_UI_Icons::print_svg( $type_icons[ $notice['type'] ] ); ?></span>
138+
<?php endif; ?>
139+
<div class="newspack-ui__snackbar__content">
140+
<?php echo wp_kses_post( $notice['message'] ); ?>
125141
</div>
126-
<?php endforeach; ?>
127-
</div>
142+
<?php if ( ! $notice['autohide'] ) : ?>
143+
<button class="newspack-ui__button newspack-ui__button--icon newspack-ui__button--ghost-dark newspack-ui__snackbar__close" aria-label="<?php esc_attr_e( 'Close', 'newspack-plugin' ); ?>" title="<?php esc_attr_e( 'Close', 'newspack-plugin' ); ?>">
144+
<?php Newspack_UI_Icons::print_svg( 'closeSmall' ); ?>
145+
</button>
146+
<?php endif; ?>
147+
</div>
148+
<?php endforeach; ?>
128149
</div>
129-
<?php
130-
}
150+
</div>
151+
<?php
131152
}
132153

133154
/**
@@ -468,69 +489,92 @@ public static function return_demo_content() {
468489

469490
<h2 id="notices">Notices</h2>
470491

471-
<div class="newspack-ui__notice">
492+
<div class="newspack-ui__notice" role="status">
472493
<div>
473494
<p>Default notice style</p>
474495
</div>
475496
</div>
476497

477-
<div class="newspack-ui__notice newspack-ui__notice--success">
498+
<div class="newspack-ui__notice newspack-ui__notice--success" role="status">
478499
<div>
479500
<p>"Success" notice style</p>
480501
</div>
481502
</div>
482503

483-
<div class="newspack-ui__notice newspack-ui__notice--warning">
504+
<div class="newspack-ui__notice newspack-ui__notice--warning" role="status">
484505
<div>
485506
<p>"Warning" notice style</p>
486507
</div>
487508
</div>
488509

489-
<div class="newspack-ui__notice newspack-ui__notice--error">
510+
<div class="newspack-ui__notice newspack-ui__notice--error" role="alert">
490511
<div>
491512
<p>"Error" notice style</p>
492513
</div>
493514
</div>
494515

495-
<div class="newspack-ui__notice">
516+
<div class="newspack-ui__notice" role="status">
496517
<?php Newspack_UI_Icons::print_svg( 'info' ); ?>
497518
<div>
498519
<p>Default notice with icon style</p>
499520
</div>
500521
</div>
501522

502-
<div class="newspack-ui__notice newspack-ui__notice--success">
523+
<div class="newspack-ui__notice newspack-ui__notice--success" role="status">
503524
<?php Newspack_UI_Icons::print_svg( 'check' ); ?>
504525
<div>
505526
<p>"Success" notice with icon style</p>
506527
</div>
507528
</div>
508529

509-
<div class="newspack-ui__notice newspack-ui__notice--warning">
530+
<div class="newspack-ui__notice newspack-ui__notice--warning" role="status">
510531
<?php Newspack_UI_Icons::print_svg( 'info' ); ?>
511532
<div>
512533
<p>"Warning" notice with icon style</p>
513534
</div>
514535
</div>
515536

516-
<div class="newspack-ui__notice newspack-ui__notice--error">
537+
<div class="newspack-ui__notice newspack-ui__notice--error" role="alert">
517538
<?php Newspack_UI_Icons::print_svg( 'error' ); ?>
518539
<div>
519540
<p>"Error" notice with icon style</p>
520541
</div>
521542
</div>
522-
<button id="show-snackbar-example" class="newspack-ui__button newspack-ui__button--primary">Show snackbar</button>
523-
<div class="newspack-ui__snackbar newspack-ui__snackbar--top-right">
524-
<div id="snackbar-example" class="newspack-ui__snackbar__item newspack-ui__snackbar__item--success" data-autohide="true">
525-
<div class="newspack-ui__snackbar__content">This is a snackbar message</div>
526-
</div>
543+
<div class="newspack-ui__stack newspack-ui__stack--horizontal">
544+
<button id="show-snackbar-example" class="newspack-ui__button newspack-ui__button--primary">Show snackbar</button>
545+
<button id="show-snackbar-warning" class="newspack-ui__button newspack-ui__button--secondary">Show warning snackbar</button>
546+
<button id="show-snackbar-persistent" class="newspack-ui__button newspack-ui__button--secondary">Show error snackbar</button>
527547
</div>
548+
<div class="newspack-ui__snackbar"></div>
549+
<template id="snackbar-persistent-template">
550+
<div class="newspack-ui__snackbar__item" data-type="error" data-autohide="false">
551+
<span class="newspack-ui__snackbar__icon"><?php Newspack_UI_Icons::print_svg( 'error' ); ?></span>
552+
<div class="newspack-ui__snackbar__content">This snackbar stays until dismissed. Please <a href="#">take action</a>.</div>
553+
<button class="newspack-ui__button newspack-ui__button--icon newspack-ui__button--ghost-dark newspack-ui__snackbar__close" aria-label="<?php esc_attr_e( 'Close', 'newspack-plugin' ); ?>" title="<?php esc_attr_e( 'Close', 'newspack-plugin' ); ?>">
554+
<?php Newspack_UI_Icons::print_svg( 'closeSmall' ); ?>
555+
</button>
556+
</div>
557+
</template>
528558
<script>
529559
( function() {
530-
const snackbar = document.getElementById( 'snackbar-example' );
531-
const button = document.getElementById( 'show-snackbar-example' );
532-
button.addEventListener( 'click', function() {
533-
newspackUI.notices.openNotice( snackbar, false );
560+
const snackbar = document.querySelector( '.newspack-ui__snackbar' );
561+
document.getElementById( 'show-snackbar-example' ).addEventListener( 'click', function() {
562+
newspackUI.notices.createNotice( 'This is a snackbar message' );
563+
} );
564+
document.getElementById( 'show-snackbar-warning' ).addEventListener( 'click', function() {
565+
newspackUI.notices.createNotice( 'This is a warning snackbar message', 'warning' );
566+
} );
567+
const template = document.getElementById( 'snackbar-persistent-template' );
568+
document.getElementById( 'show-snackbar-persistent' ).addEventListener( 'click', function() {
569+
const item = template.content.firstElementChild.cloneNode( true );
570+
snackbar.appendChild( item );
571+
item.querySelectorAll( 'a, button' ).forEach( function( el ) {
572+
el.addEventListener( 'click', function() {
573+
newspackUI.notices.closeNotice( item );
574+
} );
575+
} );
576+
void item.offsetWidth;
577+
newspackUI.notices.openNotice( item, true );
534578
} );
535579
} )();
536580
</script>

plugins/newspack-plugin/includes/content-gate/class-ip-access-rule.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,14 @@ public static function handle_result_notice() {
399399
Newspack_UI::add_notice(
400400
$message,
401401
[
402-
'type' => 'success',
403402
'autohide' => true,
404403
]
405404
);
406405
} elseif ( 'failure' === $result ) {
407406
Newspack_UI::add_notice(
408407
__( "We couldn't verify your location. Make sure you're on your organization's network and try again.", 'newspack-plugin' ),
409408
[
410-
'type' => 'warning',
409+
'type' => 'error',
411410
'autohide' => false,
412411
]
413412
);

plugins/newspack-plugin/includes/plugins/woocommerce-subscriptions/group-subscription/class-group-subscription-invite.php

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -863,49 +863,26 @@ public static function render_invite_notice() {
863863
}
864864

865865
$messages = [
866-
'link_invalid' => [
867-
'message' => __( 'This link is no longer valid. Please contact the group manager.', 'newspack-plugin' ),
868-
'type' => 'error',
869-
],
870-
'link_full' => [
871-
'message' => __( 'This group already has the maximum number of members. Please contact the group manager.', 'newspack-plugin' ),
872-
'type' => 'error',
873-
],
874-
'link_failed' => [
875-
'message' => __( "We couldn't add you to the group. Please contact the group manager.", 'newspack-plugin' ),
876-
'type' => 'error',
877-
],
878-
'login_needed' => [
879-
'message' => __( 'Please log in or register an account to join the group.', 'newspack-plugin' ),
880-
'type' => 'notice',
881-
],
882-
'error_invalid_link' => [
883-
'message' => __( 'Invalid invitation link.', 'newspack-plugin' ),
884-
'type' => 'error',
885-
],
886-
'error_email_mismatch' => [
887-
'message' => __( 'This invitation is for a different email address.', 'newspack-plugin' ),
888-
'type' => 'error',
889-
],
890-
'error_invite_invalid' => [
891-
'message' => __( 'Invalid or expired invitation.', 'newspack-plugin' ),
892-
'type' => 'error',
893-
],
894-
'error_registration_failed' => [
895-
'message' => __( 'Could not create your account. Please try again.', 'newspack-plugin' ),
896-
'type' => 'error',
897-
],
866+
'link_invalid' => __( 'This link is no longer valid. Please contact the group manager.', 'newspack-plugin' ),
867+
'link_full' => __( 'This group already has the maximum number of members. Please contact the group manager.', 'newspack-plugin' ),
868+
'link_failed' => __( "We couldn't add you to the group. Please contact the group manager.", 'newspack-plugin' ),
869+
'login_needed' => __( 'Please log in or register an account to join the group.', 'newspack-plugin' ),
870+
'error_invalid_link' => __( 'Invalid invitation link.', 'newspack-plugin' ),
871+
'error_email_mismatch' => __( 'This invitation is for a different email address.', 'newspack-plugin' ),
872+
'error_invite_invalid' => __( 'Invalid or expired invitation.', 'newspack-plugin' ),
873+
'error_registration_failed' => __( 'Could not create your account. Please try again.', 'newspack-plugin' ),
898874
];
899875

900876
if ( 'success' === $result ) {
901877
$message = __( 'You have successfully joined the group!', 'newspack-plugin' );
902878
$type = 'success';
903879
} else {
904-
$message = ! empty( $messages[ $result ]['message'] ) ? $messages[ $result ]['message'] : __( 'There was a problem with your invitation.', 'newspack-plugin' );
905-
$type = ! empty( $messages[ $result ]['type'] ) ? $messages[ $result ]['type'] : 'error';
880+
$message = ! empty( $messages[ $result ] ) ? $messages[ $result ] : __( 'There was a problem with your invitation.', 'newspack-plugin' );
881+
// 'login_needed' is an informational call to action, not an error, so it announces politely.
882+
$type = 'login_needed' === $result ? 'success' : 'error';
906883
}
907884

908-
Newspack_UI::add_notice( $message, $type );
885+
Newspack_UI::add_notice( $message, [ 'type' => $type ] );
909886
}
910887

911888
/**

plugins/newspack-plugin/includes/plugins/woocommerce/class-woocommerce-update-payment-notice.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,6 @@ public static function maybe_add_newspack_notices() {
122122
$notice,
123123
[
124124
'id' => $notice_id,
125-
'type' => 'warning',
126-
'corner' => 'top-right',
127125
'autohide' => false,
128126
]
129127
);

plugins/newspack-plugin/includes/plugins/woocommerce/my-account/class-my-account-ui-v1.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,6 @@ public static function add_after_delete_account_notice() {
572572
__( 'Your account has been deleted.', 'newspack-plugin' ),
573573
[
574574
'id' => 'after-delete-account',
575-
'type' => 'success',
576-
'corner' => 'top-right',
577575
'autohide' => true,
578576
]
579577
);

plugins/newspack-plugin/includes/plugins/woocommerce/my-account/templates/v1/group-subscription-members.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
$is_completely_empty = empty( $members ) && empty( $all_invites );
4545
?>
4646
<?php if ( ! $is_manageable ) : ?>
47-
<div class="newspack-ui__notice newspack-ui__notice--info newspack-my-account__group_subscription__inactive-notice">
47+
<div class="newspack-ui__notice newspack-ui__notice--info newspack-my-account__group_subscription__inactive-notice" role="status">
4848
<p class="newspack-ui__spacing-top--0 newspack-ui__spacing-bottom--0">
4949
<?php
5050
echo esc_html(

plugins/newspack-plugin/includes/plugins/woocommerce/my-account/templates/v1/notices/error.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
[
2121
'id' => uniqid( 'newspack-myaccount-error-' ),
2222
'type' => 'error',
23-
'corner' => 'top-right',
2423
'autohide' => ! apply_filters( 'newspack_ui_notice_is_urgent', false, $notice['notice'] ),
2524
'active_on_load' => true,
2625
]

plugins/newspack-plugin/includes/plugins/woocommerce/my-account/templates/v1/notices/notice.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
[
2222
'id' => uniqid( 'newspack-myaccount-notice-' ),
2323
'type' => 'warning',
24-
'corner' => 'top-right',
2524
'autohide' => ! apply_filters( 'newspack_ui_notice_is_urgent', false, $notice['notice'] ),
2625
'active_on_load' => true,
2726
]

0 commit comments

Comments
 (0)