Repository: expo/expo
File: docs/ui/components/Snippet/blocks/Terminal.tsx (TypeScript)

Code before:
  const { availableManagers, activeManager, activeCmd, shouldShowPackageTabs, setActiveManager } =
    usePackageManagerState(packageManagers, cmd);
  const isMobileView = useIsMobileView();

  return (
    <Snippet className={mergeClasses('terminal-snippet [li_&]:mt-4', className)}>
      <SnippetHeader alwaysDark title={title} Icon={TerminalSquareIcon}>
        <div className="flex items-center gap-2">
          {shouldShowPackageTabs &&
            (isMobileView ? (
              <PackageSelect
                managers={availableManagers}
                activeManager={activeManager}
                onSelect={setActiveManager}
              />
            ) : (
              <PackageTabs
                managers={availableManagers}
                activeManager={activeManager}
                onSelect={setActiveManager}
              />
            ))}
          {browserAction && <BrowserAction {...browserAction} />}
          <CopyButton cmd={activeCmd} cmdCopy={cmdCopy} />
        </div>
      </SnippetHeader>
      <SnippetContent alwaysDark hideOverflow={hideOverflow} className="flex flex-col">
        {activeCmd.map(cmdMapper)}
      </SnippetContent>
    </Snippet>
  );
};

/**
 * This method attempts to naively generate the basic cmdCopy from the given cmd list.
 * Currently, the implementation is simple, but we can add multiline support in the future.
 */
function getDefaultCmdCopy(cmd: TerminalProps['cmd']) {
  const validLines = cmd.filter(line => !line.startsWith('#') && line !== '');
  if (validLines.length === 1) {
    return validLines[0].startsWith('$') ? validLines[0].slice(2) : validLines[0];
  }
  return undefined;
}

const CopyButton = ({ cmd, cmdCopy }: CopyButtonProps) => {
  const copyText = cmdCopy ?? getDefaultCmdCopy(cmd);

  if (!copyText) {
    return null;
  }

Review comment (suggestion):
Can we add a fixed width to the button to ensure that the package manager list does not shift.
Alternatively could have the list at the start, sth like this:

<img width="785" height="230" alt="Image" src="https://github.com/user-attachments/assets/24e0f801-fb35-48b9-8331-ade3c8c1d50b" />

Code after:
      <PackageSelect
        managers={availableManagers}
        activeManager={activeManager}
        onSelect={setActiveManager}
        className="ml-4"
      />
    ) : (
      <PackageTabs
        managers={availableManagers}
        activeManager={activeManager}
        onSelect={setActiveManager}
        className="ml-6"
      />
    )
  ) : null;

  return (
    <Snippet className={mergeClasses('terminal-snippet [li_&]:mt-4', className)}>
      <SnippetHeader
        alwaysDark
        title={title}
        titleSlot={packageManagerSlot}
        Icon={TerminalSquareIcon}>
        <div className="flex items-center gap-2">
          {browserAction && <BrowserAction {...browserAction} />}
          <CopyButton cmd={activeCmd} cmdCopy={cmdCopy} />
        </div>
      </SnippetHeader>
      <SnippetContent alwaysDark hideOverflow={hideOverflow} className="flex flex-col">
        {activeCmd.map(cmdMapper)}
      </SnippetContent>
    </Snippet>
  );
};

/**
 * This method attempts to naively generate the basic cmdCopy from the given cmd list.
 * Currently, the implementation is simple, but we can add multiline support in the future.
 */
function getDefaultCmdCopy(cmd: string[]) {
  const validLines = cmd.filter(line => !line.startsWith('#') && line !== '');
  if (validLines.length === 1) {
    return validLines[0].startsWith('$') ? validLines[0].slice(2) : validLines[0];
  }
  return undefined;
}

const CopyButton = ({ cmd, cmdCopy }: CopyButtonProps) => {
  const copyText = cmdCopy ?? getDefaultCmdCopy(cmd);

  if (!copyText) {