MobTranslate styleguide

Commands and contextual actions

Connect discoverable commands to actual object changes, retained state and recoverable outcomes.

Match access to task scope

SurfaceUse
Direct Button / ToolbarFrequent visible action; explicit target and outcome
MenuCompact command/settings list associated with a visible trigger
Context menu plus visible alternativeObject-specific commands with equivalent keyboard/single-pointer reachability
MenubarPersistent application commands with coordinated menus
Select / Radio / CheckboxForm values and independent selection, rather than commands
Dialog / confirmation / undoConsequential decision, input, pending result or independent recovery

Working command state and recovery

Local reading record

English-only local notes are visible.

Local copies: 0.

No local command run. Reading layout: compact. Menus do not prove authority or permission.

tsx
'use client';
import { useRef, useState } from 'react';
import { Button, Menu, MenuCheckboxItem, MenuCheckboxItemIndicator, MenuGroup, MenuGroupLabel, MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuSeparator, MenuSubmenuRoot, MenuSubmenuTrigger, MenuTrigger } from '@mobtranslate/ui';

export function MenuCommandsExample() {
  const [open, setOpen] = useState(false);
  const [title, setTitle] = useState('Local reading record');
  const [copies, setCopies] = useState(0);
  const [notes, setNotes] = useState(true);
  const [layout, setLayout] = useState('compact');
  const [pending, setPending] = useState(false);
  const [result, setResult] = useState('No local command run.');
  const context = useRef<HTMLDivElement>(null);
  const submenu = useRef<HTMLElement>(null);
  function resolve(outcome: 'complete' | 'fail' | 'cancel') {
    if (!pending) return;
    if (outcome === 'complete') { setTitle('Updated local reading record'); setResult('Local title changed. No server write occurred.'); }
    if (outcome === 'fail') setResult('Local rename failed. The original title remains; retry is available.');
    if (outcome === 'cancel') setResult('Local rename cancelled. The original title remains.');
    setPending(false); context.current?.focus();
  }
  return <div data-menu-lab className="space-y-4">
    <div ref={context} tabIndex={-1} role="region" aria-label="Local command context" data-menu-record style={{lineHeight:layout === 'compact' ? 1.5 : 1.75, padding:layout === 'compact' ? 8 : 16}}>
      <p>{title}</p>{notes && <p data-command-notes>English-only local notes are visible.</p>}<p data-command-copies>Local copies: {copies}.</p>
    </div>
    <Menu open={open} onOpenChange={setOpen} disabled={pending}>
      <MenuTrigger data-command-trigger render={<Button variant="outline" disabled={pending}>Local record commands</Button>} />
      <MenuPortal><MenuPositioner sideOffset={8} align="start"><MenuPopup aria-label="Local record commands" finalFocus={context}>
        <MenuGroup><MenuGroupLabel>Local commands</MenuGroupLabel>
          <MenuItem onClick={() => { setCopies(count => count+1); setResult('Local copy added. No source was duplicated remotely.'); }}>Duplicate local record</MenuItem>
          <MenuItem onClick={() => { setPending(true); setResult('Local rename pending. Choose an explicit outcome below.'); }}>Rename local record…</MenuItem>
          <MenuItem disabled onClick={() => setResult('Unexpected unavailable command activation.')}>Publish to server (unavailable)</MenuItem>
        </MenuGroup><MenuSeparator />
        <MenuCheckboxItem checked={notes} onCheckedChange={setNotes} className={state => state.checked ? 'local-notes-checked' : undefined}><MenuCheckboxItemIndicator keepMounted aria-hidden="true">✓</MenuCheckboxItemIndicator>Show local notes</MenuCheckboxItem>
        <MenuSubmenuRoot closeParentOnEsc={false} onOpenChangeComplete={childOpen => { if (!childOpen && open) submenu.current?.focus(); }}><MenuSubmenuTrigger ref={submenu} label="Reading layout">Reading layout <span aria-hidden="true">›</span></MenuSubmenuTrigger>
          <MenuPortal><MenuPositioner side="right" align="start" sideOffset={4}><MenuPopup aria-label="Reading layout choices" finalFocus={submenu}>
            <MenuRadioGroup value={layout} onValueChange={setLayout} aria-label="Local reading density">
              <MenuRadioItem value="compact"><MenuRadioItemIndicator keepMounted aria-hidden="true">●</MenuRadioItemIndicator>Compact layout</MenuRadioItem>
              <MenuRadioItem value="comfortable"><MenuRadioItemIndicator keepMounted aria-hidden="true">●</MenuRadioItemIndicator>Comfortable layout</MenuRadioItem>
            </MenuRadioGroup>
          </MenuPopup></MenuPositioner></MenuPortal>
        </MenuSubmenuRoot>
      </MenuPopup></MenuPositioner></MenuPortal>
    </Menu>
    {pending && <div className="flex flex-wrap gap-3"><Button type="button" onClick={() => resolve('complete')}>Complete local rename</Button><Button type="button" variant="outline" onClick={() => resolve('fail')}>Fail local rename</Button><Button type="button" variant="outline" onClick={() => resolve('cancel')}>Cancel local rename</Button></div>}
    <Button type="button" variant="outline" disabled={pending} onClick={() => { setTitle('Local reading record'); setCopies(0); setResult('Restored the local record and cleared local copies.'); }}>Restore local command context</Button>
    <p role="status">{result} Reading layout: {layout}. Menus do not prove authority or permission.</p>
  </div>;
}

Implement a complete command boundary

  • Name the object and action. A detached/contextual trigger must still identify which current record it operates on.
  • Execute a real synchronous effect or snapshot a requested async operation. Close the menu according to the command, retain the old data and provide pending/recovery controls outside it.
  • Prevent duplicate operations, handle field/general failures and distinguish cancellation of a local wait from confirmed remote cancellation. Preserve operation/record identity against stale responses.
  • Expose the confirmed retained result and a surviving focus target. When removal disables/unmounts the trigger, independent restore or the next task context must remain reachable.
  • Menu settings need checked semantics and visible marks. Radio alternatives operate on one actual group; commands do not become form values just because they look like choices.

Accessibility and adaptive review

  • Check names, roles, expanded/checked/disabled states, keyboard and focus transitions against the installed version. Verify actual pointer and keyboard effects rather than only callbacks.
  • Keep touch targets reachable, menus bounded and last actions scrollable at narrow widths/zoom. Verify long press on hardware and retain a visible alternative.
  • Portals can escape local themes and containment. Check actual popup colors, focus cues, collision placement and transformed/scrolling ancestors.
  • A command result must preserve source, review and permission evidence independently. A successful local action does not establish cultural authority.

Acceptance and failure review

CheckObservable result
Open / navigate / dismissNamed list, usable keyboard/pointer movement and surviving restored focus
UnavailableActual suppression with a useful explanation; no hidden operation
Settings / submenuActual rendered state, accurate checked semantics and a return path
Pending / failed / cancelledRetained context, duplicate guard, truthful result and retry/recovery
Completed / restoredConfirmed data effect or clearly named local simulation; independent recovery remains usable

Verification and release scope

  • Verify the actual command effect, requested/retained state, recovery and focus after activation. A closed menu or click callback is not a save receipt.
  • Check trigger naming/expanded state, item roles, group names, checked states, disabled enforcement, keyboard entry/exit, type navigation, submenu return and focus restoration. Inspect the exact installed behavior rather than assuming an upstream convention.
  • Check both themes, readable text/focus cues, long labels, bounded scrolling, 320px reflow, reduced motion, forced colors, zoom and the actual touch device. Context long press is not proven by mouse tests.
  • Use the checked example sources and the menu-family browser suite, then a named screen-reader/browser pair for spoken output. These local English-only examples prove no server write, permission, source authority or qualified review.