fix(admin): keep invite filters inside the list card
Unify status filters, search, bulk actions and pagination in one invite list card. Remove the standalone filter summary and preserve usable search widths on small screens, with structural regression coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a6776ffe0a
commit
5732be847e
3 files changed
+60
-21
No files matched your search
@@ -2,10 +2,13 @@
|
||||
.aksrt-inv-page [hidden] { display: none !important; }
|
||||
.aksrt-inv-page .aksrt-native-header__actions .button { display: inline-flex; align-items: center; gap: 5px; }
|
||||
.aksrt-inv-page .aksrt-native-header__actions .dashicons { margin: 0; font-size: 17px; line-height: 20px; }
|
||||
.aksrt-inv-toolbar { padding: 14px 20px; border-bottom: 1px solid var(--an-border-soft); }
|
||||
.aksrt-inv-toolbar__views { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
|
||||
.aksrt-inv-toolbar .subsubsub { margin: 0; padding: 0; box-shadow: none; }
|
||||
.aksrt-inv-toolbar .aksrt-native-filters { margin: 0; }
|
||||
.aksrt-inv-filter-summary { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--an-border-soft); overflow-wrap: anywhere; }
|
||||
.aksrt-inv-selection { color: var(--an-text-2); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-inv-filter-remark { max-width: 240px; color: var(--an-text-2); font-size: 12px; overflow-wrap: anywhere; }
|
||||
.aksrt-inv-bulk { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 12px 20px; border-bottom: 1px solid var(--an-border-soft); }
|
||||
.aksrt-inv-selection { margin-right: auto; color: var(--an-text-2); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.aksrt-inv-page .aksrt-inv-table { min-width: 1000px; table-layout: fixed; }
|
||||
.aksrt-inv-table .column-code { width: 21%; }
|
||||
@@ -90,8 +93,10 @@ html.aksrt-inv-modal-open { overflow: hidden; }
|
||||
@media (max-width: 600px) {
|
||||
.aksrt-inv-page .aksrt-native-header__actions { width: 100%; }
|
||||
.aksrt-inv-page .aksrt-native-header__actions .button { flex: 1; justify-content: center; }
|
||||
.aksrt-inv-toolbar,
|
||||
.aksrt-inv-bulk { padding-left: 14px; padding-right: 14px; }
|
||||
.aksrt-inv-toolbar .aksrt-native-filters { width: 100%; }
|
||||
.aksrt-inv-toolbar input[type="search"] { flex: 1; width: 0; min-width: 0; }
|
||||
.aksrt-inv-toolbar input[type="search"] { flex: 1 1 calc(100% - 76px); width: 0; min-width: 0; }
|
||||
.aksrt-inv-dialog { max-height: calc(100dvh - 24px); }
|
||||
.aksrt-inv-dialog__head,
|
||||
.aksrt-inv-dialog__body,
|
||||
|
||||
@@ -81,40 +81,38 @@ trait UsersMenusInviteCode {
|
||||
$filters = ['status' => $status, 's' => $s, 'remark' => $remark, 'orderby' => $data['orderby'], 'desc' => $data['desc']];
|
||||
$filter_url = add_query_arg($filters, $this_url);
|
||||
|
||||
NativeUi::card_open('', '', false, 'aksrt-inv-toolbar');
|
||||
echo '<div class="aksrt-native-toolbar"><ul class="subsubsub" aria-label="' . esc_attr__('使用状态筛选', 'aksrt') . '">';
|
||||
NativeUi::card_open('', '', true, 'aksrt-inv-list-card');
|
||||
echo '<div class="aksrt-inv-toolbar"><div class="aksrt-native-toolbar"><div class="aksrt-inv-toolbar__views">';
|
||||
echo '<h2 class="aksrt-native-card__title">' . esc_html__('邀请码列表', 'aksrt') . '</h2>';
|
||||
echo '<ul class="subsubsub" aria-label="' . esc_attr__('使用状态筛选', 'aksrt') . '">';
|
||||
foreach (['all' => __('全部', 'aksrt'), 'unused' => __('未使用', 'aksrt'), 'used' => __('已使用', 'aksrt')] as $key => $label) {
|
||||
$url = add_query_arg('status', $key, $filter_url);
|
||||
echo '<li><a href="' . esc_url($url) . '"' . ($key === $status ? ' class="current" aria-current="page"' : '') . '>' . esc_html($label) . '</a></li>';
|
||||
}
|
||||
echo '</ul><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
|
||||
echo '</ul></div><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
|
||||
echo '<input type="hidden" name="page" value="aksrt_invite_codes">';
|
||||
foreach (array_diff_key($filters, ['s' => '']) as $key => $value) {
|
||||
echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr($value) . '">';
|
||||
}
|
||||
echo '<label class="screen-reader-text" for="aksrt-inv-search">' . esc_html__('搜索邀请码或标识', 'aksrt') . '</label>';
|
||||
echo '<input type="search" id="aksrt-inv-search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索邀请码或标识', 'aksrt') . '">';
|
||||
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button></form></div>';
|
||||
if ($s !== '' || $remark !== '' || $status !== 'all') {
|
||||
echo '<div class="aksrt-inv-filter-summary">';
|
||||
if ($s !== '') {
|
||||
echo '<span>' . esc_html(sprintf(__('关键词:%s', 'aksrt'), $s)) . '</span>';
|
||||
}
|
||||
if ($remark !== '') {
|
||||
echo '<span>' . esc_html(sprintf(__('标识:%s', 'aksrt'), $remark)) . '</span>';
|
||||
}
|
||||
echo '<a href="' . esc_url($this_url) . '">' . esc_html__('清除筛选', 'aksrt') . '</a></div>';
|
||||
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button>';
|
||||
if ($remark !== '') {
|
||||
echo '<span class="aksrt-inv-filter-remark">' . esc_html(sprintf(__('标识:%s', 'aksrt'), $remark)) . '</span>';
|
||||
}
|
||||
NativeUi::card_close();
|
||||
if ($s !== '' || $remark !== '' || $status !== 'all') {
|
||||
echo '<a class="button-link" href="' . esc_url($this_url) . '">' . esc_html__('清除筛选', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</form></div></div>';
|
||||
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" data-aksrt-inv-list'
|
||||
. ' data-confirm="' . esc_attr__('确定删除选中的 %d 个邀请码及其使用记录?此操作不可撤销。', 'aksrt') . '">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_invite_delete"><input type="hidden" name="aksrt_nonce" value="' . esc_attr($nonce) . '">';
|
||||
$bulk = '<div class="aksrt-native-inline"><span class="aksrt-inv-selection" data-aksrt-inv-selection data-label="' . esc_attr__('已选 %d 项', 'aksrt') . '" aria-live="polite"></span>'
|
||||
echo '<div class="aksrt-inv-bulk"><span class="aksrt-inv-selection" data-aksrt-inv-selection data-label="' . esc_attr__('已选 %d 项', 'aksrt') . '" aria-live="polite"></span>'
|
||||
. '<label class="screen-reader-text" for="bulk-action-selector-top">' . esc_html__('批量操作', 'aksrt') . '</label>'
|
||||
. '<select name="bulk_action" id="bulk-action-selector-top"><option value="-1">' . esc_html__('批量操作', 'aksrt') . '</option><option value="delete">' . esc_html__('删除', 'aksrt') . '</option></select>'
|
||||
. '<button type="submit" class="button" data-aksrt-inv-bulk disabled>' . esc_html__('应用', 'aksrt') . '</button></div>';
|
||||
NativeUi::table_card_open(__('邀请码列表', 'aksrt'), $bulk);
|
||||
echo '<div class="aksrt-native-table-wrap">';
|
||||
echo '<table class="widefat fixed striped aksrt-inv-table"><thead><tr><td class="manage-column column-cb check-column">';
|
||||
echo '<input type="checkbox" data-aksrt-inv-check-all aria-label="' . esc_attr__('全选本页邀请码', 'aksrt') . '"></td>';
|
||||
$columns = [
|
||||
@@ -171,8 +169,7 @@ trait UsersMenusInviteCode {
|
||||
'type' => 'plain', 'prev_text' => '«', 'next_text' => '»',
|
||||
]);
|
||||
}
|
||||
echo '</div></div>';
|
||||
echo '</div></div></form>';
|
||||
NativeUi::card_close();
|
||||
echo '</form>';
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,43 @@ function hasBooleanAttr(tag, name) {
|
||||
assert.match(tag, new RegExp(`(?:\\s|^)${name}(?:\\s|>|$)`));
|
||||
}
|
||||
|
||||
test('filters, selection, table and pagination share one list card', () => {
|
||||
const { html } = harness();
|
||||
const cards = [...html.matchAll(/<section\b[^>]*class="[^"]*\baksrt-native-card\b[^"]*"[^>]*>[\s\S]*?<\/section>/g)];
|
||||
assert.equal(cards.length, 1);
|
||||
const card = cards[0][0];
|
||||
assert.match(card, /aksrt-inv-list-card/);
|
||||
assert.match(card, /<h2 class="aksrt-native-card__title">邀请码列表<\/h2>/);
|
||||
for (const marker of ['class="subsubsub"', 'class="aksrt-native-filters"', '清除筛选', 'class="aksrt-inv-bulk"', 'data-aksrt-inv-selection', 'aksrt-inv-table', 'class="tablenav bottom"']) {
|
||||
assert.ok(card.includes(marker), `${marker} belongs to the list card`);
|
||||
}
|
||||
assert.doesNotMatch(html, /aksrt-inv-filter-summary|关键词:/);
|
||||
assert.doesNotMatch(read('assets', 'css', 'admin', 'invite-codes.css'), /aksrt-inv-filter-summary/);
|
||||
});
|
||||
|
||||
test('search and bulk actions remain independent sibling forms inside the list card', () => {
|
||||
const { html } = harness();
|
||||
let depth = 0;
|
||||
let count = 0;
|
||||
for (const [tag] of html.matchAll(/<\/?form\b[^>]*>/g)) {
|
||||
if (tag.startsWith('</')) {
|
||||
depth--;
|
||||
} else {
|
||||
count++;
|
||||
depth++;
|
||||
}
|
||||
assert.ok(depth >= 0 && depth <= 1, 'forms must never nest');
|
||||
}
|
||||
assert.equal(depth, 0);
|
||||
assert.equal(count, 4);
|
||||
const searchEnd = html.indexOf('</form>', html.indexOf('class="aksrt-native-filters"'));
|
||||
const listStart = html.lastIndexOf('<form', html.indexOf('data-aksrt-inv-list'));
|
||||
assert.ok(searchEnd < listStart);
|
||||
const listEnd = html.indexOf('</form>', listStart);
|
||||
assert.ok(html.indexOf('class="tablenav bottom"', listStart) < listEnd);
|
||||
assert.ok(listEnd < html.indexOf('</section>', listStart));
|
||||
});
|
||||
|
||||
test('page renders add and export dialogs beside, not inside, the list form', () => {
|
||||
const { html } = harness();
|
||||
const listMarker = html.indexOf('data-aksrt-inv-list');
|
||||
|
||||
Reference in new issue
Block a user