diff --git a/src/features/roles/components/RoleFormDrawer.jsx b/src/features/roles/components/RoleFormDrawer.jsx index 91d920e..1d9e6fa 100644 --- a/src/features/roles/components/RoleFormDrawer.jsx +++ b/src/features/roles/components/RoleFormDrawer.jsx @@ -1,7 +1,7 @@ -import Checkbox from "@mui/material/Checkbox"; -import Drawer from "@mui/material/Drawer"; import TextField from "@mui/material/TextField"; -import { Button } from "@/shared/ui/Button.jsx"; +import { useEffect, useState } from "react"; +import { FormDrawerShell } from "@/shared/ui/FormDrawerShell.jsx"; +import { RolePermissionEditor } from "@/features/roles/components/RolePermissionEditor.jsx"; export function RoleFormDrawer({ abilities, @@ -15,11 +15,34 @@ export function RoleFormDrawer({ permissionGroups, setForm }) { + const [activeSection, setActiveSection] = useState("profile"); + + useEffect(() => { + // 新开角色表单时先展示身份字段,避免沿用上一角色的权限分类上下文。 + if (open) { + setActiveSection("profile"); + } + }, [editingRole, open]); + return ( - -
-

{editingRole ? "编辑角色" : "新增角色"}

-
+ + {activeSection === "profile" ? ( +
基础信息
setForm({ ...form, name: event.target.value })} /> @@ -27,69 +50,17 @@ export function RoleFormDrawer({ setForm({ ...form, description: event.target.value })} />
- {abilities.canLoadPermissions ? ( -
-
权限范围
-
- {permissionGroups.map((group) => ( - - ))} -
-
- ) : null} -
- - -
- - - ); -} - -function PermissionMenuGroup({ abilities, group, onTogglePermission, onTogglePermissionGroup, permissionIds }) { - const groupPermissionIds = group.items.map((permission) => permission.id); - const selectedCount = groupPermissionIds.filter((permissionId) => permissionIds.includes(permissionId)).length; - const allSelected = selectedCount === groupPermissionIds.length; - const partiallySelected = selectedCount > 0 && !allSelected; - - return ( -
-
- {group.title} -
- {selectedCount}/{groupPermissionIds.length} - -
-
-
- {group.items.map((permission) => ( - - ))} -
-
+ ) : null} + + {activeSection === "permissions" && abilities.canLoadPermissions ? ( + + ) : null} +
); } diff --git a/src/features/roles/components/RoleFormDrawer.test.jsx b/src/features/roles/components/RoleFormDrawer.test.jsx new file mode 100644 index 0000000..2dcfa16 --- /dev/null +++ b/src/features/roles/components/RoleFormDrawer.test.jsx @@ -0,0 +1,40 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { expect, test, vi } from "vitest"; +import { RoleFormDrawer } from "./RoleFormDrawer.jsx"; + +const abilities = { canCreate: true, canGrant: true, canLoadPermissions: true, canUpdate: true }; + +test("shows role permissions as a searchable category workspace", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button", { name: "权限配置1" })); + + expect(screen.getByRole("navigation", { name: "权限分类" })).toBeInTheDocument(); + expect(screen.getByPlaceholderText("搜索权限名称或编码")).toBeInTheDocument(); + expect(screen.getByText("user:view")).toBeInTheDocument(); + expect(screen.getByText("已选 1 / 2 项")).toBeInTheDocument(); +}); diff --git a/src/features/roles/components/RolePermissionEditor.jsx b/src/features/roles/components/RolePermissionEditor.jsx new file mode 100644 index 0000000..2288fbd --- /dev/null +++ b/src/features/roles/components/RolePermissionEditor.jsx @@ -0,0 +1,147 @@ +import SearchOutlined from "@mui/icons-material/SearchOutlined"; +import Checkbox from "@mui/material/Checkbox"; +import InputAdornment from "@mui/material/InputAdornment"; +import TextField from "@mui/material/TextField"; +import { useEffect, useMemo, useState } from "react"; + +export function RolePermissionEditor({ + abilities, + onTogglePermission, + onTogglePermissionGroup, + permissionGroups, + permissionIds, +}) { + const [activeGroupKey, setActiveGroupKey] = useState(""); + const [query, setQuery] = useState(""); + const [selectedOnly, setSelectedOnly] = useState(false); + + const visibleGroups = useMemo(() => { + const keyword = query.trim().toLowerCase(); + // 全局搜索先收窄分类再展示命中项,权限 ID 集合不随筛选变化,避免隐藏项被意外清空。 + return permissionGroups + .map((group) => ({ + ...group, + visibleItems: group.items.filter((permission) => { + if (selectedOnly && !permissionIds.includes(permission.id)) { + return false; + } + return ( + !keyword || + [permission.name, permission.code].some((value) => + String(value || "") + .toLowerCase() + .includes(keyword), + ) + ); + }), + })) + .filter((group) => group.visibleItems.length > 0 || (!query && !selectedOnly)); + }, [permissionGroups, permissionIds, query, selectedOnly]); + + useEffect(() => { + // 当前分类被搜索条件过滤掉时定位首个命中分类,让右侧始终对应左侧可见导航。 + if (!visibleGroups.some((group) => group.key === activeGroupKey)) { + setActiveGroupKey(visibleGroups[0]?.key || ""); + } + }, [activeGroupKey, visibleGroups]); + + const activeGroup = visibleGroups.find((group) => group.key === activeGroupKey) || visibleGroups[0]; + + return ( +
+ + +
+
+
+ {activeGroup?.title || "权限范围"} + + 已选 {permissionIds.length} / {permissionGroups.reduce((total, group) => total + group.items.length, 0)} 项 + +
+ setQuery(event.target.value)} + placeholder="搜索权限名称或编码" + slotProps={{ + input: { + startAdornment: ( + + + + ), + }, + }} + value={query} + /> + +
+ + {activeGroup ? ( + <> +
+ {activeGroup.visibleItems.length} 项 + +
+
+ {activeGroup.visibleItems.map((permission) => ( + + ))} +
+ + ) : ( +
当前无匹配权限
+ )} +
+
+ ); +} + +function countSelected(items, permissionIds) { + return items.filter((permission) => permissionIds.includes(permission.id)).length; +} diff --git a/src/features/users/components/UserFinanceScopeEditor.jsx b/src/features/users/components/UserFinanceScopeEditor.jsx new file mode 100644 index 0000000..ad7a5aa --- /dev/null +++ b/src/features/users/components/UserFinanceScopeEditor.jsx @@ -0,0 +1,147 @@ +import SearchOutlined from "@mui/icons-material/SearchOutlined"; +import Checkbox from "@mui/material/Checkbox"; +import InputAdornment from "@mui/material/InputAdornment"; +import TextField from "@mui/material/TextField"; +import { useEffect, useMemo, useState } from "react"; +import { AppIdentity } from "@/shared/ui/AppIdentity.jsx"; + +export function UserFinanceScopeEditor({ catalog, checkedScopes, onChecked }) { + const apps = useMemo(() => catalog?.apps || [], [catalog?.apps]); + const [activeAppCode, setActiveAppCode] = useState(""); + const [query, setQuery] = useState(""); + const [selectedOnly, setSelectedOnly] = useState(false); + + useEffect(() => { + // 目录刷新后主动回退到第一个有效应用,防止权限变化导致右侧仍引用已移除的应用。 + if (!apps.some((app) => app.appCode === activeAppCode)) { + setActiveAppCode(apps[0]?.appCode || ""); + } + }, [activeAppCode, apps]); + + const activeApp = apps.find((app) => app.appCode === activeAppCode) || apps[0]; + const activeRegions = useMemo(() => { + const keyword = query.trim().toLowerCase(); + // 搜索与仅看已选只影响展示集合,原始勾选状态始终保留,切换应用或筛选不会丢失授权。 + return (catalog?.regions || []).filter((region) => { + if (region.appCode !== activeApp?.appCode) { + return false; + } + const checked = hasScope(checkedScopes, region.appCode, region.regionId); + if (selectedOnly && !checked) { + return false; + } + return !keyword || [region.name, region.regionCode].some((value) => String(value || "").toLowerCase().includes(keyword)); + }); + }, [activeApp?.appCode, catalog?.regions, checkedScopes, query, selectedOnly]); + + if (catalog?.loading) { + return
加载中
; + } + if (catalog?.error) { + return
{catalog.error}
; + } + if (!apps.length) { + return
当前无数据
; + } + + const allRegionsChecked = hasScope(checkedScopes, activeApp.appCode, 0); + + return ( +
+ + +
+
+
+ {activeApp.appName || activeApp.appCode} + 已选 {countAppScopes(checkedScopes, activeApp.appCode)} 项 +
+ setQuery(event.target.value)} + placeholder="搜索区域名称或编码" + slotProps={{ + input: { + startAdornment: ( + + + + ), + }, + }} + value={query} + /> + +
+ +
+ {(!selectedOnly || allRegionsChecked) && !query ? ( + onChecked(activeApp.appCode, 0, checked)} + /> + ) : null} + {activeRegions.map((region) => ( + onChecked(activeApp.appCode, region.regionId, checked)} + /> + ))} +
+ {!activeRegions.length && (query || selectedOnly) && !(selectedOnly && allRegionsChecked) ? ( +
当前无匹配区域
+ ) : null} +
+
+ ); +} + +function FinanceScopeOption({ checked, code, label, onChange }) { + return ( + + ); +} + +function hasScope(scopes, appCode, regionId) { + return (scopes || []).some( + (scope) => scope.appCode === appCode && Number(scope.regionId) === Number(regionId), + ); +} + +function countAppScopes(scopes, appCode) { + return (scopes || []).filter((scope) => scope.appCode === appCode).length; +} diff --git a/src/features/users/components/UserFormDrawer.jsx b/src/features/users/components/UserFormDrawer.jsx index ea19dda..94e715c 100644 --- a/src/features/users/components/UserFormDrawer.jsx +++ b/src/features/users/components/UserFormDrawer.jsx @@ -1,9 +1,10 @@ -import Drawer from "@mui/material/Drawer"; +import Checkbox from "@mui/material/Checkbox"; import MenuItem from "@mui/material/MenuItem"; -import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx"; import TextField from "@mui/material/TextField"; -import { Button } from "@/shared/ui/Button.jsx"; -import { AppIdentity } from "@/shared/ui/AppIdentity.jsx"; +import { useEffect, useState } from "react"; +import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx"; +import { FormDrawerShell } from "@/shared/ui/FormDrawerShell.jsx"; +import { UserFinanceScopeEditor } from "@/features/users/components/UserFinanceScopeEditor.jsx"; export function UserFormDrawer({ editingUser, @@ -18,71 +19,91 @@ export function UserFormDrawer({ setForm, teams = [] }) { + const [activeSection, setActiveSection] = useState("profile"); + + useEffect(() => { + // 每次切换编辑对象都回到账号资料,避免复用抽屉时停留在上一位用户的授权页造成误操作。 + if (open) { + setActiveSection("profile"); + } + }, [editingUser, open]); + + const sections = [ + { key: "profile", label: "账号资料" }, + { count: form.financeScopes?.length || 0, key: "finance", label: "财务范围" }, + { count: form.roleIds?.length || 0, key: "access", label: "角色与安全" } + ]; + return ( - -
-

{editingUser ? "编辑用户" : "新增用户"}

-
+ + {activeSection === "profile" ? ( +
+
基础信息
+
+ setForm({ ...form, username: event.target.value })} + /> + setForm({ ...form, name: event.target.value })} /> + { + const teamId = event.target.value; + const team = teams.find((item) => String(item.id) === String(teamId)); + setForm({ ...form, team: team?.name || "", teamId }); + }} + > + 未选择 + {teams.map((team) => ( + + {team.parentName ? `${team.parentName} / ${team.name}` : team.name} + + ))} + + {!editingUser ? ( + setForm({ ...form, password: event.target.value })} /> + ) : null} +
+
+ ) : null} + + {activeSection === "finance" ? ( + + ) : null} + + {activeSection === "access" ? ( +
-
基础信息
-
- setForm({ ...form, username: event.target.value })} - /> - setForm({ ...form, name: event.target.value })} /> - { - const teamId = event.target.value; - const team = teams.find((item) => String(item.id) === String(teamId)); - setForm({ ...form, team: team?.name || "", teamId }); - }} - > - 未选择 - {teams.map((team) => ( - - {team.parentName ? `${team.parentName} / ${team.name}` : team.name} - - ))} - - {!editingUser ? ( - setForm({ ...form, password: event.target.value })} /> - ) : null} +
+
角色
+ 已选 {form.roleIds.length} 项
-
-
-
财务范围
-
- {financeScopeCatalog?.loading ? 加载中 : null} - {financeScopeCatalog?.error ? {financeScopeCatalog.error} : null} - {!financeScopeCatalog?.loading && !financeScopeCatalog?.error - ? (financeScopeCatalog?.apps || []).map((app) => ( - region.appCode === app.appCode)} - /> - )) - : null} -
-
-
-
角色
-
+
{roles.map((role) => ( - @@ -91,46 +112,22 @@ export function UserFormDrawer({
安全设置
- setForm({ ...form, mfaEnabled: event.target.checked })} - /> +
+
+ MFA 状态 + 登录时进行多因素认证 +
+ setForm({ ...form, mfaEnabled: event.target.checked })} + /> +
-
- - -
- - - ); -} - -function FinanceScopeGroup({ app, checkedScopes, onChecked, regions }) { - const appCode = app.appCode; - const isChecked = (regionId) => - checkedScopes.some((scope) => scope.appCode === appCode && Number(scope.regionId) === Number(regionId)); - - return ( -
- - - {regions.map((region) => ( - - ))} -
+ ) : null} +
); } diff --git a/src/features/users/components/UserFormDrawer.test.jsx b/src/features/users/components/UserFormDrawer.test.jsx index 9b4deab..c54e097 100644 --- a/src/features/users/components/UserFormDrawer.test.jsx +++ b/src/features/users/components/UserFormDrawer.test.jsx @@ -1,4 +1,5 @@ import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { expect, test, vi } from "vitest"; import { UserFormDrawer } from "./UserFormDrawer.jsx"; @@ -19,7 +20,8 @@ const baseFinanceScopeCatalog = { regions: [{ appCode: "Huwaa", name: "菲律宾", regionCode: "PHILIPPINES", regionId: 8 }] }; -test("keeps edit user drawer actions docked outside the scrollable content", () => { +test("organizes edit user fields into dense workspace sections with docked actions", async () => { + const user = userEvent.setup(); render( +
+
+
+

{title}

+ {summary ? {summary} : null} +
+ + + +
+ + + +
{children}
+ +
+ + +
+
+ + ); +} diff --git a/src/styles/shared-ui.css b/src/styles/shared-ui.css index adb03ae..46cc6be 100644 --- a/src/styles/shared-ui.css +++ b/src/styles/shared-ui.css @@ -939,6 +939,472 @@ padding-top: var(--space-2); } +/* 高密度编辑统一使用固定标题、分区导航和操作区,避免长表单把提交按钮推离视口。 */ +.form-drawer--workspace { + box-sizing: border-box; + width: min(900px, calc(100vw - 48px)); + height: 100vh; + max-height: 100vh; + grid-template-rows: auto auto minmax(0, 1fr) auto; + gap: 0; + overflow: hidden; + padding: var(--space-5); + background: var(--bg-card); +} + +.role-form-workspace { + width: min(1180px, calc(100vw - 48px)); +} + +.form-drawer__header { + display: flex; + min-height: 44px; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-4); +} + +.form-drawer__heading { + display: grid; + min-width: 0; + gap: 2px; +} + +.form-drawer__heading h2 { + margin: 0; +} + +.form-drawer__heading > span { + overflow: hidden; + color: var(--text-tertiary); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.form-drawer__tabs { + display: flex; + min-height: 42px; + align-items: flex-end; + gap: var(--space-5); + border-bottom: 1px solid var(--border); +} + +.form-drawer__tabs button { + position: relative; + display: inline-flex; + min-height: 42px; + align-items: center; + gap: var(--space-2); + padding: 0 var(--space-1); + border: 0; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + font-weight: 650; + transition: color var(--motion-fast) var(--ease-standard); +} + +.form-drawer__tabs button::after { + position: absolute; + right: 0; + bottom: -1px; + left: 0; + height: 2px; + background: var(--primary); + content: ""; + opacity: 0; + transform: scaleX(0.65); + transition: + opacity var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.form-drawer__tabs button:hover, +.form-drawer__tabs button.is-active { + color: var(--primary); +} + +.form-drawer__tabs button.is-active::after { + opacity: 1; + transform: scaleX(1); +} + +.form-drawer__tabs small { + display: grid; + min-width: 20px; + height: 20px; + padding: 0 6px; + place-items: center; + border-radius: var(--radius-pill); + background: var(--neutral-surface); + color: var(--text-tertiary); + line-height: 20px; +} + +.form-drawer__tabs button.is-active small { + background: var(--primary-surface); + color: var(--primary); +} + +.form-drawer__workspace { + min-height: 0; + overflow: auto; + padding: var(--space-4) 2px; +} + +.form-drawer--workspace .form-drawer__panel { + align-content: start; + min-height: 100%; +} + +.form-drawer--workspace .form-drawer__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.form-drawer--workspace .form-drawer__actions { + padding-top: var(--space-3); + border-top: 1px solid var(--border); + background: var(--bg-card); +} + +.form-drawer__section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.form-drawer__section-heading > span { + color: var(--text-tertiary); + font-size: 12px; +} + +.form-drawer__access-panel { + display: grid; + align-content: start; + gap: var(--space-5); +} + +.form-setting-row { + display: flex; + min-height: 56px; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); +} + +.form-setting-row > div { + display: grid; + gap: 2px; +} + +.form-setting-row strong { + color: var(--text-primary); +} + +.form-setting-row span { + color: var(--text-tertiary); + font-size: 12px; +} + +/* 主从分栏让用户先定位应用或权限分类,再编辑单个范围,内容量增长时仍保持稳定扫读路径。 */ +.form-editor-split { + display: grid; + min-height: 100%; + grid-template-columns: 220px minmax(0, 1fr); + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.form-editor-split--permissions { + grid-template-columns: 210px minmax(0, 1fr); +} + +.form-editor-rail { + min-width: 0; + overflow: auto; + border-right: 1px solid var(--border); + background: var(--bg-card-strong); +} + +.form-editor-rail__label { + padding: var(--space-3); + color: var(--text-tertiary); + font-size: 12px; + font-weight: 700; +} + +.form-editor-rail button { + display: flex; + width: 100%; + min-height: 42px; + align-items: center; + justify-content: space-between; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border: 0; + border-left: 2px solid transparent; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + text-align: left; + transition: + background var(--motion-fast) var(--ease-standard), + border-color var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard); +} + +.form-editor-rail button:hover { + background: var(--bg-hover); +} + +.form-editor-rail button.is-active { + border-left-color: var(--primary); + background: var(--primary-surface); + color: var(--primary); + font-weight: 700; +} + +.form-editor-rail button > :first-child { + min-width: 0; +} + +.form-editor-rail__count { + flex: 0 0 auto; + color: var(--text-tertiary); + font-size: 12px; + font-variant-numeric: tabular-nums; +} + +.form-editor-panel { + display: grid; + min-width: 0; + min-height: 0; + align-content: start; + overflow: auto; +} + +.form-editor-toolbar { + position: sticky; + z-index: 1; + top: 0; + display: grid; + min-height: 60px; + grid-template-columns: minmax(120px, 1fr) minmax(220px, 300px) auto; + align-items: center; + gap: var(--space-3); + padding: var(--space-3); + border-bottom: 1px solid var(--border); + background: var(--bg-card); +} + +.form-editor-toolbar > div:first-child { + display: grid; + min-width: 0; + gap: 2px; +} + +.form-editor-toolbar strong { + overflow: hidden; + color: var(--text-primary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.form-editor-toolbar > div:first-child > span { + color: var(--text-tertiary); + font-size: 12px; +} + +.form-editor-filter, +.form-editor-group-actions label { + display: inline-flex; + align-items: center; + gap: var(--space-1); + color: var(--text-secondary); + white-space: nowrap; +} + +.form-editor-filter .MuiCheckbox-root, +.form-editor-group-actions .MuiCheckbox-root, +.form-option .MuiCheckbox-root { + padding: 0; +} + +.form-editor-group-actions { + display: flex; + min-height: 40px; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + padding: 0 var(--space-3); + border-bottom: 1px solid var(--border-soft); + color: var(--text-tertiary); + font-size: 12px; +} + +.form-option-grid { + display: grid; + gap: var(--space-2); + padding: var(--space-3); +} + +.form-option-grid--finance, +.form-option-grid--roles { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.form-option-grid--permissions { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.form-option { + display: flex; + min-width: 0; + min-height: 48px; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-card-strong); + color: var(--text-secondary); + cursor: pointer; + transition: + background var(--motion-fast) var(--ease-standard), + border-color var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.form-option:hover { + border-color: var(--primary-border-strong); + background: var(--primary-hover); +} + +.form-option:active { + transform: scale(0.99); +} + +.form-option > span { + display: grid; + min-width: 0; + gap: 2px; +} + +.form-option strong, +.form-option small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.form-option strong { + color: var(--text-primary); + font-size: var(--control-font-size); + font-weight: 650; +} + +.form-option small { + color: var(--text-tertiary); + font-size: 11px; +} + +.form-option--single-line { + min-height: 40px; +} + +.form-option--single-line > span { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.form-editor-state { + display: grid; + min-height: 160px; + place-items: center; + color: var(--text-tertiary); +} + +.form-editor-state--error { + color: var(--danger); +} + +@media (max-width: 980px) { + .form-drawer--workspace, + .role-form-workspace { + width: 100vw; + padding: var(--space-4); + } + + .form-editor-split, + .form-editor-split--permissions { + grid-template-columns: 180px minmax(0, 1fr); + } + + .form-option-grid--permissions { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 720px) { + .form-drawer--workspace .form-drawer__grid, + .form-option-grid--finance, + .form-option-grid--roles, + .form-option-grid--permissions { + grid-template-columns: 1fr; + } + + .form-editor-split, + .form-editor-split--permissions { + grid-template-columns: 1fr; + overflow: visible; + } + + .form-editor-rail { + display: flex; + max-height: none; + overflow-x: auto; + border-right: 0; + border-bottom: 1px solid var(--border); + } + + .form-editor-rail__label { + display: none; + } + + .form-editor-rail button { + width: auto; + min-width: max-content; + border-bottom: 2px solid transparent; + border-left: 0; + } + + .form-editor-rail button.is-active { + border-bottom-color: var(--primary); + } + + .form-editor-toolbar { + position: static; + grid-template-columns: 1fr; + } +} + +@media (prefers-reduced-motion: reduce) { + .form-drawer__tabs button::after, + .form-editor-rail button, + .form-option { + transition: none; + } +} + .reward-tier-editor-table { display: grid; min-width: 0;