Refine routing rule controls and responsive layout
Build and Deploy Gateway / build-and-push (push) Successful in 35s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-17 16:04:06 +03:00
parent 7c255192b0
commit bc86741397
9 changed files with 227 additions and 109 deletions
+41 -23
View File
@@ -19,6 +19,7 @@ import { operationBlocked } from '../../state/operations.js';
import { ConfirmationDialog } from '../../ui/ConfirmationDialog.js';
import { Drawer } from '../../ui/Drawer.js';
import { RailAction } from '../../ui/RailAction.js';
import { Tooltip } from '../../ui/Tooltip.js';
import {
beginRuleReorder,
crossedRuleIndex,
@@ -760,19 +761,26 @@ function RuleOutboundPicker({
disabled?: boolean;
onChange: (value: RouteRule['outbound']) => void;
}) {
return <div className="client-rule-outbound" role="group" aria-label={`Маршрут правила ${index + 1}`}>
{([['vpn', 'VPN'], ['direct', 'Напрямую']] as const).map(([outbound, label]) => (
<button
type="button"
key={outbound}
aria-pressed={value === outbound}
disabled={disabled}
onClick={() => onChange(outbound)}
>
{label}
</button>
))}
</div>;
const direct = value === 'direct';
const label = direct ? 'Напрямую' : 'VPN';
return <span className="client-rule-outbound client-tooltip-anchor">
<button
className={direct ? 'is-direct' : 'is-vpn'}
type="button"
aria-label={`Маршрут правила ${index + 1}: ${label}. Переключить на ${direct ? 'VPN' : 'Напрямую'}`}
aria-pressed={direct}
disabled={disabled}
onClick={() => onChange(direct ? 'vpn' : 'direct')}
>
{direct ? <svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 12h15M14 7l5 5-5 5" />
</svg> : <svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3 19 6v5c0 4.4-2.8 8-7 10-4.2-2-7-5.6-7-10V6l7-3Z" />
<path d="m9 12 2 2 4-4" />
</svg>}
</button>
<Tooltip>{label}</Tooltip>
</span>;
}
export function RoutingToggle({
@@ -899,7 +907,11 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
</span>
<section className="client-local-rules-group" aria-labelledby="local-rules-list-title">
<span id="local-rules-list-title">Правила</span>
<div className="client-local-rules-list">
<div
className={`client-local-rules-list${draftRules.length > 1 ? ' has-order-flow' : ''}`}
role="list"
aria-label="Правила применяются сверху вниз"
>
{feature.rules.map((rule, index) => {
const [status, statusLabel] = localRuleStatus(
rule,
@@ -916,6 +928,7 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
className={`client-local-rule client-deletable-row is-${status}${rule.enabled ? '' : ' is-disabled'}${rule.removing ? ' is-removing' : ''}${lifted ? ' is-dragging' : ''}`}
key={rule._key}
data-rule-key={rule._key}
role="listitem"
style={{ viewTransitionName: rule.removing ? 'none' : rule._key }}
inert={rule.removing ? true : undefined}
>
@@ -974,6 +987,20 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
value={rule.value}
onChange={(event) => feature.change(index, 'value', event.target.value)}
/>
<RuleOutboundPicker
value={rule.outbound}
index={index}
disabled={editorDisabled}
onChange={(outbound) => feature.change(index, 'outbound', outbound)}
/>
<span
className="client-local-rule-status client-tooltip-anchor"
role="status"
aria-label={`Состояние правила: ${statusLabel}`}
>
<span className="client-rule-status-dot" aria-hidden="true" />
<Tooltip>{statusLabel}</Tooltip>
</span>
<button
className="client-row-delete"
type="button"
@@ -988,15 +1015,6 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
aria-hidden="true"
onAnimationEnd={() => feature.finishRemove(rule._key)}
/>
<div className="client-local-rule-meta">
<RuleOutboundPicker
value={rule.outbound}
index={index}
disabled={editorDisabled}
onChange={(outbound) => feature.change(index, 'outbound', outbound)}
/>
<span className="client-local-rule-status" role="status">{statusLabel}</span>
</div>
</div>
);
})}