feat: improve bookmark dialog UX with distinct modes, inline validation, and accessibility
Refine bookmark create/edit and group create/edit dialogs to enterprise quality: distinct titles per mode (Add bookmark vs Edit bookmark), URL preview when creating, inline field validation with aria-invalid instead of toast-only errors, loading spinner on submit, compact inline new-group link, human-readable icon picker aria-labels, and aria-pressed on selected icons. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,15 +6,40 @@ $layoutNav = is_array($layoutNav ?? null) ? $layoutNav : [];
|
||||
$bookmarkNav = is_array($layoutNav['bookmarks.nav'] ?? null) ? $layoutNav['bookmarks.nav'] : [];
|
||||
$bookmarkData = is_array($bookmarkNav['grouped'] ?? null) ? $bookmarkNav['grouped'] : [];
|
||||
$bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['groups'] : [];
|
||||
|
||||
// Human-readable icon labels — keep in sync with BookmarkService::ALLOWED_GROUP_ICONS
|
||||
$iconLabels = [
|
||||
'bi-house' => t('Icon: Home'),
|
||||
'bi-people' => t('Icon: People'),
|
||||
'bi-person' => t('Icon: Person'),
|
||||
'bi-folder' => t('Icon: Folder'),
|
||||
'bi-file-text' => t('Icon: Document'),
|
||||
'bi-gear' => t('Icon: Settings'),
|
||||
'bi-shield-lock' => t('Icon: Security'),
|
||||
'bi-bar-chart' => t('Icon: Chart'),
|
||||
'bi-envelope' => t('Icon: Mail'),
|
||||
'bi-calendar' => t('Icon: Calendar'),
|
||||
'bi-clock' => t('Icon: Clock'),
|
||||
'bi-star' => t('Icon: Star'),
|
||||
'bi-heart' => t('Icon: Heart'),
|
||||
'bi-flag' => t('Icon: Flag'),
|
||||
'bi-bookmark' => t('Icon: Bookmark'),
|
||||
'bi-pin-map' => t('Icon: Location'),
|
||||
'bi-lightning' => t('Icon: Lightning'),
|
||||
'bi-database' => t('Icon: Database'),
|
||||
'bi-globe' => t('Icon: Globe'),
|
||||
'bi-code-slash' => t('Icon: Code'),
|
||||
];
|
||||
?>
|
||||
<dialog
|
||||
data-app-bookmark-dialog
|
||||
data-app-component="bookmark-save"
|
||||
data-max-message="<?php e(t('Maximum bookmarks reached')); ?>"
|
||||
data-title-create="<?php e(t('Bookmarks')); ?>"
|
||||
data-title-edit="<?php e(t('Bookmarks')); ?>"
|
||||
data-label-save="<?php e(t('Save')); ?>"
|
||||
data-label-update="<?php e(t('Save')); ?>"
|
||||
data-title-create="<?php e(t('Add bookmark')); ?>"
|
||||
data-title-edit="<?php e(t('Edit bookmark')); ?>"
|
||||
data-label-save="<?php e(t('Add bookmark')); ?>"
|
||||
data-label-update="<?php e(t('Save changes')); ?>"
|
||||
data-label-saving="<?php e(t('Saving...')); ?>"
|
||||
data-msg-saved="<?php e(t('Bookmark saved')); ?>"
|
||||
data-msg-updated="<?php e(t('Bookmark updated')); ?>"
|
||||
data-msg-group-created="<?php e(t('Group created')); ?>"
|
||||
@@ -23,18 +48,27 @@ $bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['gro
|
||||
data-msg-name-required="<?php e(t('Bookmark name is required')); ?>"
|
||||
data-msg-url-required="<?php e(t('Bookmark URL is invalid')); ?>"
|
||||
data-msg-group-not-found="<?php e(t('Group not found')); ?>"
|
||||
data-url-preview-label="<?php e(t('Bookmarking this page:')); ?>"
|
||||
aria-labelledby="app-bookmark-dialog-title"
|
||||
>
|
||||
<article class="app-bookmark-dialog">
|
||||
<header>
|
||||
<h2 id="app-bookmark-dialog-title"><?php e(t('Bookmarks')); ?></h2>
|
||||
<h2 id="app-bookmark-dialog-title"><?php e(t('Add bookmark')); ?></h2>
|
||||
<button type="button" class="close" data-bookmark-dialog-close aria-label="<?php e(t('Close')); ?>" title="<?php e(t('Close')); ?>"></button>
|
||||
</header>
|
||||
<form data-bookmark-save-form>
|
||||
<small class="app-bookmark-url-preview" data-bookmark-url-preview hidden></small>
|
||||
<label for="bookmark-name"><?php e(t('Name')); ?></label>
|
||||
<input type="text" id="bookmark-name" name="name" maxlength="120" required
|
||||
placeholder="<?php e(t('Bookmark name')); ?>">
|
||||
<label for="bookmark-group"><?php e(t('Assign to group')); ?></label>
|
||||
placeholder="<?php e(t('Bookmark name')); ?>"
|
||||
aria-describedby="bookmark-name-error">
|
||||
<small class="app-bookmark-field-error" id="bookmark-name-error" data-field-error="name"></small>
|
||||
<div class="app-bookmark-group-label-row">
|
||||
<label for="bookmark-group"><?php e(t('Assign to group')); ?></label>
|
||||
<button type="button" class="app-bookmark-new-group-link" data-bookmark-new-group>
|
||||
<i class="bi bi-plus"></i> <?php e(t('New group')); ?>
|
||||
</button>
|
||||
</div>
|
||||
<select id="bookmark-group" name="group_id" data-bookmark-group-select>
|
||||
<option value=""><?php e(t('No group')); ?></option>
|
||||
<?php foreach ($bookmarkGroups as $group):
|
||||
@@ -45,12 +79,9 @@ $bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['gro
|
||||
<option value="<?php e($groupId); ?>"><?php e($groupName); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="button" class="outline secondary app-bookmark-new-group-btn" data-bookmark-new-group>
|
||||
<i class="bi bi-plus"></i> <?php e(t('New group')); ?>
|
||||
</button>
|
||||
<footer>
|
||||
<button type="button" class="secondary outline" data-bookmark-dialog-close><?php e(t('Cancel')); ?></button>
|
||||
<button type="submit" class="app-action-success" data-bookmark-submit-label><?php e(t('Save')); ?></button>
|
||||
<button type="submit" class="app-action-success" data-bookmark-submit-label><?php e(t('Add bookmark')); ?></button>
|
||||
</footer>
|
||||
</form>
|
||||
</article>
|
||||
@@ -60,8 +91,9 @@ $bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['gro
|
||||
data-app-bookmark-group-dialog
|
||||
data-title-create="<?php e(t('New group')); ?>"
|
||||
data-title-edit="<?php e(t('Edit group')); ?>"
|
||||
data-label-save="<?php e(t('Save')); ?>"
|
||||
data-label-update="<?php e(t('Save')); ?>"
|
||||
data-label-save="<?php e(t('Create group')); ?>"
|
||||
data-label-update="<?php e(t('Save changes')); ?>"
|
||||
data-label-saving="<?php e(t('Saving...')); ?>"
|
||||
data-max-message="<?php e(t('Maximum groups reached')); ?>"
|
||||
data-msg-created="<?php e(t('Group created')); ?>"
|
||||
data-msg-updated="<?php e(t('Group updated')); ?>"
|
||||
@@ -77,13 +109,18 @@ $bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['gro
|
||||
<form data-bookmark-group-save-form>
|
||||
<label for="bookmark-group-name"><?php e(t('Group name')); ?></label>
|
||||
<input type="text" id="bookmark-group-name" name="name" maxlength="100" required
|
||||
placeholder="<?php e(t('Group name')); ?>">
|
||||
placeholder="<?php e(t('Group name')); ?>"
|
||||
aria-describedby="bookmark-group-name-error">
|
||||
<small class="app-bookmark-field-error" id="bookmark-group-name-error" data-field-error="group-name"></small>
|
||||
<label><?php e(t('Icon')); ?></label>
|
||||
<div class="app-bookmark-group-icon-picker" data-bookmark-group-icon-picker>
|
||||
<?php foreach (BookmarkService::allowedGroupIcons() as $allowedIcon): ?>
|
||||
<?php foreach (BookmarkService::allowedGroupIcons() as $allowedIcon):
|
||||
$readableLabel = $iconLabels[$allowedIcon] ?? $allowedIcon;
|
||||
?>
|
||||
<button type="button" class="app-bookmark-group-icon-option"
|
||||
data-icon="<?php e($allowedIcon); ?>"
|
||||
aria-label="<?php e($allowedIcon); ?>">
|
||||
aria-label="<?php e($readableLabel); ?>"
|
||||
aria-pressed="false">
|
||||
<i class="bi <?php e($allowedIcon); ?>"></i>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
@@ -91,7 +128,7 @@ $bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['gro
|
||||
<input type="hidden" name="icon" value="bi-folder">
|
||||
<footer>
|
||||
<button type="button" class="secondary outline" data-bookmark-group-dialog-close><?php e(t('Cancel')); ?></button>
|
||||
<button type="submit" class="app-action-success" data-bookmark-group-submit-label><?php e(t('Save')); ?></button>
|
||||
<button type="submit" class="app-action-success" data-bookmark-group-submit-label><?php e(t('Create group')); ?></button>
|
||||
</footer>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user