Pages: hover hints are the site's tooltip; disabled buttons say why on focus and tap
Commit messages on branches, tags, compare and commits; package digests and badges; the repo tabs' descriptions; and the buttons disabled for want of a permission (Plan it, Turn on deployments, Save, Merge, Unlink, the upkeep switch) now use Hint, the disabled ones through its focusable wrapper. The compact deploy buttons get an aria-label, which their title had stood in for.
22 files+237−1810/22 viewed
| 8 | 8 | import { page } from "../../lib/meta"; | |
| 9 | 9 | import { LogText, Notes, StatusIcon, duration, shortRef, standingWord, useJobLog } from "../../components/actions"; | |
| 10 | 10 | import { ErrorText, SubmitButton, TimeAgo, usePending } from "../../components/ui"; | |
| 11 | + | import { Hint } from "../../components/ui/hint"; | |
| 11 | 12 | import { listArtifacts } from "../../lib/artifacts.server"; | |
| 12 | 13 | import { actions } from "../../lib/services.server"; | |
| 13 | 14 | import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 106 | 107 | ); | |
| 107 | 108 | // A link to the runners for those who manage them, a label for everyone else. | |
| 108 | 109 | return runnersPage ? ( | |
| 109 | − | <Link to={`/${workspace}/-/runners`} className={`${badge} hover:text-fg`} title="Self-hosted runner"> | |
| 110 | − | {label} | |
| 111 | − | </Link> | |
| 110 | + | <Hint label="Self-hosted runner"> | |
| 111 | + | <Link to={`/${workspace}/-/runners`} className={`${badge} hover:text-fg`}> | |
| 112 | + | {label} | |
| 113 | + | </Link> | |
| 114 | + | </Hint> | |
| 112 | 115 | ) : ( | |
| 113 | − | <span className={badge} title="Self-hosted runner"> | |
| 114 | − | {label} | |
| 115 | − | </span> | |
| 116 | + | <Hint label="Self-hosted runner"> | |
| 117 | + | <span className={badge}>{label}</span> | |
| 118 | + | </Hint> | |
| 116 | 119 | ); | |
| 117 | 120 | } | |
| 118 | 121 | if (!job.startedAt) return null; | |
| 4 | 4 | import type { Route } from "./+types/branches"; | |
| 5 | 5 | import { ActiveBranches } from "../../components/branches"; | |
| 6 | 6 | import { Avatar, EmptyState, TimeAgo, notACredential } from "../../components/ui"; | |
| 7 | + | import { Hint } from "../../components/ui/hint"; | |
| 7 | 8 | import { readBranches } from "../../lib/branches.server"; | |
| 8 | 9 | import { page } from "../../lib/meta"; | |
| 9 | 10 | import { deployments, repos, work } from "../../lib/services.server"; | |
| ⋯ | |||
| 96 | 97 | <Avatar name={head.author} size={13} /> | |
| 97 | 98 | <span className="shrink-0">{head.author}</span> | |
| 98 | 99 | <span className="text-faint">·</span> | |
| 99 | − | <Link to={`${base}/commit/${head.hash}`} className="min-w-0 truncate hover:text-fg" title={head.message}> | |
| 100 | − | {head.message} | |
| 101 | − | </Link> | |
| 100 | + | <Hint label={head.message}> | |
| 101 | + | <Link to={`${base}/commit/${head.hash}`} className="min-w-0 truncate hover:text-fg"> | |
| 102 | + | {head.message} | |
| 103 | + | </Link> | |
| 104 | + | </Hint> | |
| 102 | 105 | <span className="shrink-0 text-faint"> | |
| 103 | 106 | · <TimeAgo at={head.at} /> | |
| 104 | 107 | </span> | |
| 9 | 9 | import { page } from "../../lib/meta"; | |
| 10 | 10 | import { DiffView } from "../../components/diff-view"; | |
| 11 | 11 | import { Avatar, TimeAgo } from "../../components/ui"; | |
| 12 | + | import { Hint } from "../../components/ui/hint"; | |
| 12 | 13 | import { Skeleton } from "../../components/ui/skeleton"; | |
| 13 | 14 | import { immutable } from "../../lib/immutable.server"; | |
| 14 | 15 | import { pullForCommit } from "../../lib/provenance.server"; | |
| ⋯ | |||
| 157 | 158 | <span className="flex items-center gap-2"> | |
| 158 | 159 | <Avatar name={owner?.username ?? commit.author.name} image={owner?.avatar} size={20} /> | |
| 159 | 160 | {owner ? ( | |
| 160 | − | <Link to={`/u/${owner.username}`} className="font-medium hover:underline" title={commit.author.name}> | |
| 161 | − | {owner.username} | |
| 162 | − | </Link> | |
| 161 | + | <Hint label={commit.author.name}> | |
| 162 | + | <Link to={`/u/${owner.username}`} className="font-medium hover:underline"> | |
| 163 | + | {owner.username} | |
| 164 | + | </Link> | |
| 165 | + | </Hint> | |
| 163 | 166 | ) : ( | |
| 164 | 167 | <span className="font-medium">{commit.author.name}</span> | |
| 165 | 168 | )} | |
| 6 | 6 | import type { Route } from "./+types/commits"; | |
| 7 | 7 | import { page } from "../../lib/meta"; | |
| 8 | 8 | import { Avatar, EmptyState, TimeAgo } from "../../components/ui"; | |
| 9 | + | import { Hint } from "../../components/ui/hint"; | |
| 9 | 10 | import { accounts, repos } from "../../lib/services.server"; | |
| 10 | 11 | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 11 | 12 | ||
| ⋯ | |||
| 75 | 76 | {rest && <p className="mt-1 line-clamp-1 text-sm text-muted">{rest}</p>} | |
| 76 | 77 | <p className="mt-1 text-xs text-faint"> | |
| 77 | 78 | {owner ? ( | |
| 78 | − | <Link to={`/u/${owner.username}`} className="relative z-10 text-muted hover:text-fg" title={commit.author.name}> | |
| 79 | − | {owner.username} | |
| 80 | − | </Link> | |
| 79 | + | <Hint label={commit.author.name}> | |
| 80 | + | <Link to={`/u/${owner.username}`} className="relative z-10 text-muted hover:text-fg"> | |
| 81 | + | {owner.username} | |
| 82 | + | </Link> | |
| 83 | + | </Hint> | |
| 81 | 84 | ) : ( | |
| 82 | 85 | commit.author.name | |
| 83 | 86 | )}{" "} | |
| 6 | 6 | import type { Route } from "./+types/compare"; | |
| 7 | 7 | import { DiffView } from "../../components/diff-view"; | |
| 8 | 8 | import { Avatar, ButtonLink, EmptyState, TimeAgo } from "../../components/ui"; | |
| 9 | + | import { Hint } from "../../components/ui/hint"; | |
| 9 | 10 | import { page } from "../../lib/meta"; | |
| 10 | 11 | import { repos } from "../../lib/services.server"; | |
| 11 | 12 | import { getViewer, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 133 | 134 | <li key={commit.hash} className="flex items-center gap-3 px-4 py-2.5 text-sm"> | |
| 134 | 135 | <GitCommitHorizontal size={15} className="shrink-0 text-faint" /> | |
| 135 | 136 | <Avatar name={commit.author} size={16} /> | |
| 136 | − | <Link to={`${repoBase}/commit/${commit.hash}`} className="min-w-0 grow truncate hover:text-accent" title={commit.message}> | |
| 137 | − | {commit.message} | |
| 138 | − | </Link> | |
| 137 | + | <Hint label={commit.message}> | |
| 138 | + | <Link to={`${repoBase}/commit/${commit.hash}`} className="min-w-0 grow truncate hover:text-accent"> | |
| 139 | + | {commit.message} | |
| 140 | + | </Link> | |
| 141 | + | </Hint> | |
| 139 | 142 | <span className="hidden shrink-0 text-xs text-muted sm:inline">{commit.author}</span> | |
| 140 | 143 | <Link to={`${repoBase}/commit/${commit.hash}`} className="shrink-0 font-mono text-xs text-faint hover:text-fg"> | |
| 141 | 144 | {commit.hash.slice(0, 7)} | |
| 9 | 9 | import type { Route } from "./+types/deployments"; | |
| 10 | 10 | import { page } from "../../lib/meta"; | |
| 11 | 11 | import { ButtonLink, ComputeNote, EmptyState, ErrorText, SubmitButton, TimeAgo } from "../../components/ui"; | |
| 12 | + | import { Hint } from "../../components/ui/hint"; | |
| 12 | 13 | import { computeNoteFor } from "../../lib/compute.server"; | |
| 13 | 14 | import { billing, deployments, projects } from "../../lib/services.server"; | |
| 14 | 15 | import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 132 | 133 | </p> | |
| 133 | 134 | ) : ( | |
| 134 | 135 | <Form method="post" className="mt-4"> | |
| 135 | − | <SubmitButton | |
| 136 | − | variant="accent" | |
| 137 | − | name="intent" | |
| 138 | − | value="enable" | |
| 139 | − | pending="Turning on…" | |
| 140 | − | disabled={!can.manage_integrations} | |
| 141 | − | title={whyNot(can, "manage_integrations")} | |
| 142 | − | > | |
| 143 | − | <Rocket size={14} /> | |
| 144 | − | Turn on deployments | |
| 145 | − | </SubmitButton> | |
| 136 | + | <Hint label={whyNot(can, "manage_integrations")} disabled={!can.manage_integrations}> | |
| 137 | + | <SubmitButton variant="accent" name="intent" value="enable" pending="Turning on…" disabled={!can.manage_integrations}> | |
| 138 | + | <Rocket size={14} /> | |
| 139 | + | Turn on deployments | |
| 140 | + | </SubmitButton> | |
| 141 | + | </Hint> | |
| 146 | 142 | </Form> | |
| 147 | 143 | )} | |
| 148 | 144 | </section> | |
| ⋯ | |||
| 297 | 293 | <Form method="post" className="flex items-center gap-2"> | |
| 298 | 294 | {branch != null && <input type="hidden" name="branch" value={branch} />} | |
| 299 | 295 | <input type="hidden" name="app" value={app} /> | |
| 300 | − | <SubmitButton | |
| 301 | − | variant="quiet" | |
| 302 | − | name="intent" | |
| 303 | − | value="redeploy" | |
| 304 | − | match={{ app }} | |
| 305 | − | icon={compact} | |
| 306 | − | pending="Redeploying…" | |
| 307 | − | title="Build again from the current head" | |
| 308 | − | > | |
| 309 | − | <RotateCw size={13} /> | |
| 310 | − | {!compact && "Redeploy"} | |
| 311 | − | </SubmitButton> | |
| 312 | − | {up && ( | |
| 296 | + | <Hint label="Build again from the current head"> | |
| 313 | 297 | <SubmitButton | |
| 314 | 298 | variant="quiet" | |
| 315 | 299 | name="intent" | |
| 316 | − | value="take-down" | |
| 300 | + | value="redeploy" | |
| 317 | 301 | match={{ app }} | |
| 318 | 302 | icon={compact} | |
| 319 | − | pending="Taking down…" | |
| 320 | − | title="Take it down now" | |
| 303 | + | pending="Redeploying…" | |
| 304 | + | aria-label={compact ? "Redeploy" : undefined} | |
| 321 | 305 | > | |
| 322 | − | <Trash2 size={13} /> | |
| 323 | − | {!compact && "Take down"} | |
| 306 | + | <RotateCw size={13} /> | |
| 307 | + | {!compact && "Redeploy"} | |
| 324 | 308 | </SubmitButton> | |
| 309 | + | </Hint> | |
| 310 | + | {up && ( | |
| 311 | + | <Hint label="Take it down now"> | |
| 312 | + | <SubmitButton | |
| 313 | + | variant="quiet" | |
| 314 | + | name="intent" | |
| 315 | + | value="take-down" | |
| 316 | + | match={{ app }} | |
| 317 | + | icon={compact} | |
| 318 | + | pending="Taking down…" | |
| 319 | + | aria-label={compact ? "Take down" : undefined} | |
| 320 | + | > | |
| 321 | + | <Trash2 size={13} /> | |
| 322 | + | {!compact && "Take down"} | |
| 323 | + | </SubmitButton> | |
| 324 | + | </Hint> | |
| 325 | 325 | )} | |
| 326 | 326 | </Form> | |
| 327 | 327 | ); | |
| 7 | 7 | import { page } from "../../lib/meta"; | |
| 8 | 8 | import { ButtonLink, ComputeNote, EmptyState, ErrorText, SubmitButton, TimeAgo } from "../../components/ui"; | |
| 9 | 9 | import { Checkbox } from "../../components/ui/checkbox"; | |
| 10 | + | import { Hint } from "../../components/ui/hint"; | |
| 10 | 11 | import { | |
| 11 | 12 | Assignee, | |
| 12 | 13 | AssigneeStack, | |
| ⋯ | |||
| 279 | 280 | <span className="mt-0.5 flex shrink-0 items-center gap-3 text-xs text-muted"> | |
| 280 | 281 | <AssigneeStack people={issue.assignees} /> | |
| 281 | 282 | {issue.pullCount > 0 && ( | |
| 282 | − | <span | |
| 283 | − | className="flex items-center gap-1" | |
| 284 | − | title={`${issue.pullCount} pull ${issue.pullCount === 1 ? "request" : "requests"}`} | |
| 285 | − | > | |
| 286 | − | <GitPullRequest size={13} /> | |
| 287 | − | {issue.pullCount} | |
| 288 | − | </span> | |
| 283 | + | <Hint label={`${issue.pullCount} pull ${issue.pullCount === 1 ? "request" : "requests"}`}> | |
| 284 | + | <span className="flex items-center gap-1"> | |
| 285 | + | <GitPullRequest size={13} /> | |
| 286 | + | {issue.pullCount} | |
| 287 | + | <span className="sr-only">pull {issue.pullCount === 1 ? "request" : "requests"}</span> | |
| 288 | + | </span> | |
| 289 | + | </Hint> | |
| 289 | 290 | )} | |
| 290 | 291 | {issue.commentCount > 0 && ( | |
| 291 | 292 | <span className="flex items-center gap-1"> | |
| 10 | 10 | import { page } from "../../lib/meta"; | |
| 11 | 11 | import { type Tab as PageTab, tabsFor } from "../../lib/project-nav"; | |
| 12 | 12 | import { Pill, SoonPill } from "../../components/ui"; | |
| 13 | + | import { Hint } from "../../components/ui/hint"; | |
| 13 | 14 | import { TabStrip } from "../../components/ui/tab-strip"; | |
| 14 | 15 | import { WatchMenu } from "../../components/notifications"; | |
| 15 | 16 | import { PinButton } from "../../components/pin-button"; | |
| ⋯ | |||
| 144 | 145 | return ( | |
| 145 | 146 | <TabStrip label="Views" className="-mb-px gap-1"> | |
| 146 | 147 | {tabs.map((tab) => ( | |
| 147 | − | <Link | |
| 148 | − | key={tab.path} | |
| 149 | − | to={`${base}/${tab.path}`} | |
| 150 | − | title={tab.about} | |
| 151 | − | prefetch="intent" | |
| 152 | − | // Current by the tab's own rule: a link's address alone would also | |
| 153 | − | // mark Overview current on every page under it. | |
| 154 | − | aria-current={current(tab) ? "page" : undefined} | |
| 155 | − | data-active={current(tab) || undefined} | |
| 156 | − | className={`flex items-center gap-1.5 border-b-2 px-3 pt-1 pb-2.5 text-sm transition-colors ${ | |
| 157 | − | current(tab) | |
| 158 | − | ? "border-accent font-medium text-fg" | |
| 159 | − | : tab.soon | |
| 160 | − | ? "border-transparent text-faint hover:text-muted" | |
| 161 | − | : "border-transparent text-muted hover:text-fg" | |
| 162 | − | }`} | |
| 163 | − | > | |
| 164 | − | {tab.label} | |
| 165 | − | {tab.soon && <SoonPill />} | |
| 166 | − | </Link> | |
| 148 | + | <Hint key={tab.path} label={tab.about}> | |
| 149 | + | <Link | |
| 150 | + | to={`${base}/${tab.path}`} | |
| 151 | + | prefetch="intent" | |
| 152 | + | // Current by the tab's own rule: a link's address alone would also | |
| 153 | + | // mark Overview current on every page under it. | |
| 154 | + | aria-current={current(tab) ? "page" : undefined} | |
| 155 | + | data-active={current(tab) || undefined} | |
| 156 | + | className={`flex items-center gap-1.5 border-b-2 px-3 pt-1 pb-2.5 text-sm transition-colors ${ | |
| 157 | + | current(tab) | |
| 158 | + | ? "border-accent font-medium text-fg" | |
| 159 | + | : tab.soon | |
| 160 | + | ? "border-transparent text-faint hover:text-muted" | |
| 161 | + | : "border-transparent text-muted hover:text-fg" | |
| 162 | + | }`} | |
| 163 | + | > | |
| 164 | + | {tab.label} | |
| 165 | + | {tab.soon && <SoonPill />} | |
| 166 | + | </Link> | |
| 167 | + | </Hint> | |
| 167 | 168 | ))} | |
| 168 | 169 | </TabStrip> | |
| 169 | 170 | ); | |
| 43 | 43 | import { type ActiveBranch, ActiveBranches } from "../../components/branches"; | |
| 44 | 44 | import { ProductionChecklist } from "../../components/checklist"; | |
| 45 | 45 | import { PackageIcon } from "../../components/package-icon"; | |
| 46 | + | import { Hint } from "../../components/ui/hint"; | |
| 46 | 47 | import { Loading, Skeleton, SkeletonRows } from "../../components/ui/skeleton"; | |
| 47 | 48 | import { ProductionShot } from "../../components/production-shot"; | |
| 48 | 49 | import { GithubLinkStrip } from "../../components/github"; | |
| ⋯ | |||
| 421 | 422 | <span className="font-mono text-faint">#{card.number}</span> | |
| 422 | 423 | <span className="grow" /> | |
| 423 | 424 | {card.needsYou ? ( | |
| 424 | − | <span className="inline-flex items-center gap-1 text-warn" title="Stopped: a person decides what happens next"> | |
| 425 | − | <Hand size={11} /> you | |
| 426 | − | </span> | |
| 425 | + | <Hint label="Stopped: a person decides what happens next"> | |
| 426 | + | <span className="inline-flex items-center gap-1 text-warn"> | |
| 427 | + | <Hand size={11} /> you | |
| 428 | + | <span className="sr-only">: stopped, a person decides what happens next</span> | |
| 429 | + | </span> | |
| 430 | + | </Hint> | |
| 427 | 431 | ) : stage === "landed" ? ( | |
| 428 | 432 | <TimeAgo at={card.at} /> | |
| 429 | 433 | ) : card.runStarted ? ( | |
| ⋯ | |||
| 436 | 440 | )} | |
| 437 | 441 | </span> | |
| 438 | 442 | {card.step && ( | |
| 439 | − | <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70" title={card.step}> | |
| 440 | − | {card.runKind}: {card.step} | |
| 441 | − | </span> | |
| 443 | + | <Hint label={card.step}> | |
| 444 | + | <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70"> | |
| 445 | + | {card.runKind}: {card.step} | |
| 446 | + | </span> | |
| 447 | + | </Hint> | |
| 442 | 448 | )} | |
| 443 | 449 | </Link> | |
| 444 | 450 | </li> | |
| ⋯ | |||
| 771 | 777 | )} | |
| 772 | 778 | {settings?.enabled ? ( | |
| 773 | 779 | loaderData.can.run && <Form method="post"> | |
| 774 | − | <SubmitButton | |
| 775 | − | variant="quiet" | |
| 776 | − | name="intent" | |
| 777 | − | value="redeploy" | |
| 778 | − | pending="Redeploying…" | |
| 779 | − | title="Build production again from the default branch" | |
| 780 | − | > | |
| 781 | − | <RotateCw size={14} /> | |
| 782 | − | Redeploy | |
| 783 | − | </SubmitButton> | |
| 780 | + | <Hint label="Build production again from the default branch"> | |
| 781 | + | <SubmitButton variant="quiet" name="intent" value="redeploy" pending="Redeploying…"> | |
| 782 | + | <RotateCw size={14} /> | |
| 783 | + | Redeploy | |
| 784 | + | </SubmitButton> | |
| 785 | + | </Hint> | |
| 784 | 786 | </Form> | |
| 785 | 787 | ) : loaderData.can.manage_integrations && ( | |
| 786 | 788 | <ButtonLink to={`${base}/settings/deployments`} variant="accent"> | |
| ⋯ | |||
| 829 | 831 | to={commit ? `${base}/commit/${commit.hash}` : undefined} | |
| 830 | 832 | value={ | |
| 831 | 833 | commit ? ( | |
| 832 | − | <span title={commit.message.split("\n")[0]}> | |
| 833 | − | <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "} | |
| 834 | − | <span className="font-normal text-muted"> | |
| 835 | − | <TimeAgo at={commit.authoredAt} /> | |
| 834 | + | <Hint label={commit.message.split("\n")[0]}> | |
| 835 | + | <span> | |
| 836 | + | <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "} | |
| 837 | + | <span className="font-normal text-muted"> | |
| 838 | + | <TimeAgo at={commit.authoredAt} /> | |
| 839 | + | </span> | |
| 836 | 840 | </span> | |
| 837 | − | </span> | |
| 841 | + | </Hint> | |
| 838 | 842 | ) : ( | |
| 839 | 843 | "No commits yet" | |
| 840 | 844 | ) | |
| ⋯ | |||
| 897 | 901 | {run.title ?? "A run"} {run.number != null && <span className="text-faint">#{run.number}</span>} | |
| 898 | 902 | </Link> | |
| 899 | 903 | {run.step && ( | |
| 900 | − | <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70" title={run.step}> | |
| 901 | − | <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" /> | |
| 902 | − | {run.step} | |
| 903 | − | </p> | |
| 904 | + | <Hint label={run.step}> | |
| 905 | + | <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70"> | |
| 906 | + | <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" /> | |
| 907 | + | {run.step} | |
| 908 | + | </p> | |
| 909 | + | </Hint> | |
| 904 | 910 | )} | |
| 905 | 911 | <p className="mt-1.5 flex items-center gap-3 text-[0.6875rem] text-faint"> | |
| 906 | 912 | {formatCost(run.costUsd) && <span>{formatCost(run.costUsd)} so far</span>} | |
| 12 | 12 | import { Outcome } from "../../components/outcome"; | |
| 13 | 13 | import { ErrorText, SubmitButton, TimeAgo, usePending } from "../../components/ui"; | |
| 14 | 14 | import { Checkbox } from "../../components/ui/checkbox"; | |
| 15 | + | import { Hint } from "../../components/ui/hint"; | |
| 15 | 16 | import { Label } from "../../components/work"; | |
| 16 | 17 | import { billing, events, identity, work } from "../../lib/services.server"; | |
| 17 | 18 | import { | |
| ⋯ | |||
| 263 | 264 | ||
| 264 | 265 | {plan.status === "ready" && ( | |
| 265 | 266 | <div className="mt-6 flex flex-wrap items-center gap-3"> | |
| 266 | − | <SubmitButton variant="accent" name="action" value="assign" pending="Opening issues…" disabled={applying || !loaderData.can.run} title={whyNot(loaderData.can, "run")}> | |
| 267 | − | <Sparkles size={15} /> | |
| 268 | − | Open these and assign g1t | |
| 269 | − | </SubmitButton> | |
| 270 | − | <SubmitButton variant="quiet" name="action" value="open" pending="Opening issues…" disabled={applying || !loaderData.can.run} title={whyNot(loaderData.can, "run")}> | |
| 271 | − | Only open the issues | |
| 272 | − | </SubmitButton> | |
| 267 | + | <Hint label={whyNot(loaderData.can, "run")} disabled={!loaderData.can.run}> | |
| 268 | + | <SubmitButton variant="accent" name="action" value="assign" pending="Opening issues…" disabled={applying || !loaderData.can.run}> | |
| 269 | + | <Sparkles size={15} /> | |
| 270 | + | Open these and assign g1t | |
| 271 | + | </SubmitButton> | |
| 272 | + | </Hint> | |
| 273 | + | <Hint label={whyNot(loaderData.can, "run")} disabled={!loaderData.can.run}> | |
| 274 | + | <SubmitButton variant="quiet" name="action" value="open" pending="Opening issues…" disabled={applying || !loaderData.can.run}> | |
| 275 | + | Only open the issues | |
| 276 | + | </SubmitButton> | |
| 277 | + | </Hint> | |
| 273 | 278 | <span className="text-xs text-muted"> | |
| 274 | 279 | Untick any you do not want. Agents work on the independent ones at once | |
| 275 | 280 | and the rest follow as what they depend on merges. | |
| 9 | 9 | import { whyNot } from "../../lib/access"; | |
| 10 | 10 | import { page } from "../../lib/meta"; | |
| 11 | 11 | import { ComputeNote, EmptyState, ErrorText, SubmitButton, Textarea, TimeAgo } from "../../components/ui"; | |
| 12 | + | import { Hint } from "../../components/ui/hint"; | |
| 12 | 13 | import { computeNoteFor } from "../../lib/compute.server"; | |
| 13 | 14 | import { work } from "../../lib/services.server"; | |
| 14 | 15 | import { | |
| ⋯ | |||
| 79 | 80 | } | |
| 80 | 81 | /> | |
| 81 | 82 | <div className="flex flex-wrap items-center gap-3"> | |
| 82 | − | <SubmitButton variant="accent" pending="Starting the planner…" disabled={!loaderData.can.run} title={whyNot(loaderData.can, "run")}> | |
| 83 | − | <Sparkles size={15} /> | |
| 84 | − | Plan it | |
| 85 | − | </SubmitButton> | |
| 83 | + | <Hint label={whyNot(loaderData.can, "run")} disabled={!loaderData.can.run}> | |
| 84 | + | <SubmitButton variant="accent" pending="Starting the planner…" disabled={!loaderData.can.run}> | |
| 85 | + | <Sparkles size={15} /> | |
| 86 | + | Plan it | |
| 87 | + | </SubmitButton> | |
| 88 | + | </Hint> | |
| 86 | 89 | <span className="text-xs text-muted"> | |
| 87 | 90 | Nothing is opened until you have read the plan. | |
| 88 | 91 | </span> | |
| 63 | 63 | usePending, | |
| 64 | 64 | } from "../../components/ui"; | |
| 65 | 65 | import { CheckboxOption } from "../../components/ui/checkbox"; | |
| 66 | + | import { Hint } from "../../components/ui/hint"; | |
| 66 | 67 | import { Loading, SkeletonLine } from "../../components/ui/skeleton"; | |
| 67 | 68 | import { TabStrip } from "../../components/ui/tab-strip"; | |
| 68 | 69 | import { WorkflowStatuses } from "../../components/actions"; | |
| ⋯ | |||
| 1213 | 1214 | /> | |
| 1214 | 1215 | )} | |
| 1215 | 1216 | <div className="flex flex-wrap items-center gap-3"> | |
| 1216 | − | <SubmitButton | |
| 1217 | − | variant="accent" | |
| 1218 | − | name="action" | |
| 1219 | − | value="merge" | |
| 1220 | − | disabled={mergeBlocked != null} | |
| 1221 | − | title={mergeBlocked ?? undefined} | |
| 1222 | − | pending={mergeQueue ? "Adding to the queue…" : "Merging…"} | |
| 1223 | − | > | |
| 1224 | − | {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />} | |
| 1225 | − | {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`} | |
| 1226 | − | </SubmitButton> | |
| 1217 | + | <Hint label={mergeBlocked} disabled={mergeBlocked != null}> | |
| 1218 | + | <SubmitButton | |
| 1219 | + | variant="accent" | |
| 1220 | + | name="action" | |
| 1221 | + | value="merge" | |
| 1222 | + | disabled={mergeBlocked != null} | |
| 1223 | + | pending={mergeQueue ? "Adding to the queue…" : "Merging…"} | |
| 1224 | + | > | |
| 1225 | + | {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />} | |
| 1226 | + | {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`} | |
| 1227 | + | </SubmitButton> | |
| 1228 | + | </Hint> | |
| 1227 | 1229 | <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}> | |
| 1228 | 1230 | {mergeBlocked ?? (mergeQueue | |
| 1229 | 1231 | ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.` | |
| 5 | 5 | import { page } from "../../lib/meta"; | |
| 6 | 6 | import { openedBy } from "../../lib/opened-by"; | |
| 7 | 7 | import { ButtonLink, EmptyState, TimeAgo } from "../../components/ui"; | |
| 8 | + | import { Hint } from "../../components/ui/hint"; | |
| 8 | 9 | import { CheckBadge } from "../../components/checks"; | |
| 9 | 10 | import { ChangeSize, PullIcon, StateTabs } from "../../components/work"; | |
| 10 | 11 | import { AgentBadge, useActiveRuns } from "../../components/agents"; | |
| ⋯ | |||
| 112 | 113 | }))} | |
| 113 | 114 | /> | |
| 114 | 115 | {loaderData.base && ( | |
| 115 | − | <Link | |
| 116 | − | to={withFilter(list, current, "base", null)} | |
| 117 | − | className="inline-flex h-8 items-center gap-1.5 rounded-md border border-accent/40 bg-accent/5 px-2.5 text-sm" | |
| 118 | − | title="Show pull requests into every branch" | |
| 119 | − | > | |
| 120 | − | Into <span className="font-mono">{loaderData.base}</span> | |
| 121 | − | </Link> | |
| 116 | + | <Hint label="Show pull requests into every branch"> | |
| 117 | + | <Link | |
| 118 | + | to={withFilter(list, current, "base", null)} | |
| 119 | + | className="inline-flex h-8 items-center gap-1.5 rounded-md border border-accent/40 bg-accent/5 px-2.5 text-sm" | |
| 120 | + | > | |
| 121 | + | Into <span className="font-mono">{loaderData.base}</span> | |
| 122 | + | <span className="sr-only">: show pull requests into every branch</span> | |
| 123 | + | </Link> | |
| 124 | + | </Hint> | |
| 122 | 125 | )} | |
| 123 | 126 | {filtered && ( | |
| 124 | 127 | <Link to={state === "closed" ? `${list}?state=closed` : list} className="text-xs text-muted hover:text-fg"> | |
| 5 | 5 | import type { Route } from "./+types/security-settings"; | |
| 6 | 6 | import { page } from "../../lib/meta"; | |
| 7 | 7 | import { ActivationPrompt, CARD, SectionHeader } from "../../components/security-suite"; | |
| 8 | + | import { Hint } from "../../components/ui/hint"; | |
| 8 | 9 | import { Switch } from "../../components/ui/switch"; | |
| 9 | 10 | import { securitySuite } from "../../lib/services.server"; | |
| 10 | 11 | import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 122 | 123 | </label> | |
| 123 | 124 | </fieldset> | |
| 124 | 125 | <div className="flex flex-wrap items-center gap-3"> | |
| 125 | − | <button | |
| 126 | − | type="submit" | |
| 127 | − | disabled={disabled || fetcher.state !== "idle"} | |
| 128 | − | title={whyNot(can, "manage_settings")} | |
| 129 | − | className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-white disabled:opacity-50" | |
| 130 | − | > | |
| 131 | − | {fetcher.state !== "idle" ? "Saving…" : "Save"} | |
| 132 | − | </button> | |
| 126 | + | <Hint label={whyNot(can, "manage_settings")} disabled={!can.manage_settings}> | |
| 127 | + | <button | |
| 128 | + | type="submit" | |
| 129 | + | disabled={disabled || fetcher.state !== "idle"} | |
| 130 | + | className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-white disabled:opacity-50" | |
| 131 | + | > | |
| 132 | + | {fetcher.state !== "idle" ? "Saving…" : "Save"} | |
| 133 | + | </button> | |
| 134 | + | </Hint> | |
| 133 | 135 | <Link to={`${base}/settings/branches`} className="text-sm text-muted underline underline-offset-2 hover:text-fg"> | |
| 134 | 136 | Require the checks in branch protection | |
| 135 | 137 | </Link> | |
| 13 | 13 | type UpgradeFix, | |
| 14 | 14 | VulnerabilityList, | |
| 15 | 15 | } from "../../components/security"; | |
| 16 | + | import { Hint } from "../../components/ui/hint"; | |
| 16 | 17 | import { Switch } from "../../components/ui/switch"; | |
| 17 | 18 | import { security, work } from "../../lib/services.server"; | |
| 18 | 19 | import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 205 | 206 | </span> | |
| 206 | 207 | {upkeep.data?.error && <span className="mt-1 block text-xs text-danger">{upkeep.data.error}</span>} | |
| 207 | 208 | </span> | |
| 208 | − | <Switch | |
| 209 | − | className="mt-0.5" | |
| 210 | − | checked={upkeepOn} | |
| 211 | − | disabled={upkeep.state !== "idle" || !can.manage_settings} | |
| 212 | − | title={whyNot(can, "manage_settings")} | |
| 213 | − | onCheckedChange={(checked) => upkeep.submit({ intent: "upkeep", enabled: String(checked) }, { method: "post", action })} | |
| 214 | − | /> | |
| 209 | + | <Hint label={whyNot(can, "manage_settings")} disabled={!can.manage_settings}> | |
| 210 | + | <Switch | |
| 211 | + | className="mt-0.5" | |
| 212 | + | checked={upkeepOn} | |
| 213 | + | disabled={upkeep.state !== "idle" || !can.manage_settings} | |
| 214 | + | onCheckedChange={(checked) => upkeep.submit({ intent: "upkeep", enabled: String(checked) }, { method: "post", action })} | |
| 215 | + | /> | |
| 216 | + | </Hint> | |
| 215 | 217 | </label> | |
| 216 | 218 | </div> | |
| 217 | 219 | </section> | |
| 7 | 7 | import { page } from "../../lib/meta"; | |
| 8 | 8 | import { RunCard, formatCost, useLiveRefresh } from "../../components/agents"; | |
| 9 | 9 | import { Avatar, TimeAgo } from "../../components/ui"; | |
| 10 | + | import { Hint } from "../../components/ui/hint"; | |
| 10 | 11 | import { agents } from "../../lib/services.server"; | |
| 11 | 12 | import { getViewer, unwrap } from "../../lib/session.server"; | |
| 12 | 13 | import { accessTo } from "../../lib/access.server"; | |
| ⋯ | |||
| 57 | 58 | <li className="flex gap-2.5 px-4 py-2 text-xs"> | |
| 58 | 59 | <Wrench size={13} className="mt-0.5 shrink-0 text-faint" /> | |
| 59 | 60 | <span className="shrink-0 font-medium text-muted">{entry.tool}</span> | |
| 60 | − | <code className="min-w-0 grow truncate font-mono text-fg/85" title={entry.text}> | |
| 61 | − | {entry.text} | |
| 62 | − | </code> | |
| 61 | + | <Hint label={entry.text}> | |
| 62 | + | <code className="min-w-0 grow truncate font-mono text-fg/85">{entry.text}</code> | |
| 63 | + | </Hint> | |
| 63 | 64 | </li> | |
| 64 | 65 | ); | |
| 65 | 66 | } | |
| 7 | 7 | import { page } from "../../lib/meta"; | |
| 8 | 8 | import { RepoSettingsHeading } from "../../components/repo-settings-heading"; | |
| 9 | 9 | import { Avatar, EmptyState, ErrorText, Field, Input, SubmitButton } from "../../components/ui"; | |
| 10 | + | import { Hint } from "../../components/ui/hint"; | |
| 10 | 11 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select"; | |
| 11 | 12 | import { projects } from "../../lib/services.server"; | |
| 12 | 13 | import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 53 | 54 | <span className="text-xs text-faint">no variable</span> | |
| 54 | 55 | )} | |
| 55 | 56 | {link.source === "file" && ( | |
| 56 | − | <span className="inline-flex items-center gap-1 text-xs text-faint" title="Declared in .g1t/project.yml"> | |
| 57 | − | <FileCode2 size={12} /> | |
| 58 | − | project.yml | |
| 59 | − | </span> | |
| 57 | + | <Hint label="Declared in .g1t/project.yml"> | |
| 58 | + | <span className="inline-flex items-center gap-1 text-xs text-faint"> | |
| 59 | + | <FileCode2 size={12} /> | |
| 60 | + | <span className="sr-only">Declared in .g1t/</span> | |
| 61 | + | project.yml | |
| 62 | + | </span> | |
| 63 | + | </Hint> | |
| 60 | 64 | )} | |
| 61 | 65 | {removable && link.source === "ui" && ( | |
| 62 | 66 | <Form method="post" className="ml-auto"> | |
| 20 | 20 | AlertDialogTrigger, | |
| 21 | 21 | } from "../../components/ui/alert-dialog"; | |
| 22 | 22 | import { CheckboxOption } from "../../components/ui/checkbox"; | |
| 23 | + | import { Hint } from "../../components/ui/hint"; | |
| 23 | 24 | import { deployments } from "../../lib/services.server"; | |
| 24 | 25 | import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server"; | |
| 25 | 26 | import { requireCapability, requireInsider } from "../../lib/access.server"; | |
| ⋯ | |||
| 238 | 239 | <Form method="post"> | |
| 239 | 240 | <input type="hidden" name="intent" value="refresh" /> | |
| 240 | 241 | <input type="hidden" name="id" value={domain.id} /> | |
| 241 | − | <SubmitButton | |
| 242 | − | variant="quiet" | |
| 243 | − | match={{ intent: "refresh", id: domain.id }} | |
| 244 | − | pending="Checking…" | |
| 245 | − | title="Ask Cloudflare to check the records again now" | |
| 246 | − | > | |
| 247 | − | <RotateCw size={14} /> | |
| 248 | − | Check now | |
| 249 | − | </SubmitButton> | |
| 242 | + | <Hint label="Ask Cloudflare to check the records again now"> | |
| 243 | + | <SubmitButton variant="quiet" match={{ intent: "refresh", id: domain.id }} pending="Checking…"> | |
| 244 | + | <RotateCw size={14} /> | |
| 245 | + | Check now | |
| 246 | + | </SubmitButton> | |
| 247 | + | </Hint> | |
| 250 | 248 | </Form> | |
| 251 | 249 | )} | |
| 252 | 250 | <RemoveDomain domain={domain} /> | |
| 3 | 3 | ||
| 4 | 4 | import type { Route } from "./+types/tags"; | |
| 5 | 5 | import { Avatar, EmptyState, TimeAgo } from "../../components/ui"; | |
| 6 | + | import { Hint } from "../../components/ui/hint"; | |
| 6 | 7 | import { page } from "../../lib/meta"; | |
| 7 | 8 | import { repos } from "../../lib/services.server"; | |
| 8 | 9 | import { getViewer, unwrap } from "../../lib/session.server"; | |
| ⋯ | |||
| 44 | 45 | <Avatar name={tag.commit.author.name} size={13} /> | |
| 45 | 46 | <span className="shrink-0">{tag.commit.author.name}</span> | |
| 46 | 47 | <span className="text-faint">·</span> | |
| 47 | − | <Link to={`${base}/commit/${tag.commit.hash}`} className="min-w-0 truncate hover:text-fg" title={tag.commit.message}> | |
| 48 | − | {tag.commit.message.split("\n")[0]} | |
| 49 | − | </Link> | |
| 48 | + | <Hint label={tag.commit.message}> | |
| 49 | + | <Link to={`${base}/commit/${tag.commit.hash}`} className="min-w-0 truncate hover:text-fg"> | |
| 50 | + | {tag.commit.message.split("\n")[0]} | |
| 51 | + | </Link> | |
| 52 | + | </Hint> | |
| 50 | 53 | <span className="shrink-0 text-faint"> | |
| 51 | 54 | · <TimeAgo at={tag.commit.authoredAt} /> | |
| 52 | 55 | </span> | |
| 3 | 3 | import type { Route } from "./+types/github"; | |
| 4 | 4 | import { page } from "../../lib/meta"; | |
| 5 | 5 | import { ErrorText, SubmitButton, TimeAgo } from "../../components/ui"; | |
| 6 | + | import { Hint } from "../../components/ui/hint"; | |
| 6 | 7 | import { assertSameOrigin, requireUser } from "../../lib/session.server"; | |
| 7 | 8 | import { GithubMark } from "../../components/github"; | |
| 8 | 9 | import { githubSignIn } from "../../lib/github.server"; | |
| ⋯ | |||
| 54 | 55 | )} | |
| 55 | 56 | <Form method="post"> | |
| 56 | 57 | <input type="hidden" name="intent" value="unlink-github" /> | |
| 57 | − | <SubmitButton variant="quiet" pending="Unlinking…" match={{ intent: "unlink-github" }} disabled={!github.hasPassword} title={github.hasPassword ? undefined : "GitHub is how you sign in. Set a password first."}> | |
| 58 | − | Unlink | |
| 59 | − | </SubmitButton> | |
| 58 | + | <Hint label={github.hasPassword ? undefined : "GitHub is how you sign in. Set a password first."} disabled={!github.hasPassword}> | |
| 59 | + | <SubmitButton variant="quiet" pending="Unlinking…" match={{ intent: "unlink-github" }} disabled={!github.hasPassword}> | |
| 60 | + | Unlink | |
| 61 | + | </SubmitButton> | |
| 62 | + | </Hint> | |
| 60 | 63 | </Form> | |
| 61 | 64 | </div> | |
| 62 | 65 | </div> | |
| 9 | 9 | import { ConfirmDialog } from "../../components/repo-lifecycle"; | |
| 10 | 10 | import { Button, CopyLine, ErrorText, SubmitButton, TimeAgo } from "../../components/ui"; | |
| 11 | 11 | import { Badge } from "../../components/ui/badge"; | |
| 12 | + | import { Hint } from "../../components/ui/hint"; | |
| 12 | 13 | import { page } from "../../lib/meta"; | |
| 13 | 14 | import { ECOSYSTEM_LABEL, formatBytes, installCommands, shortDigest } from "../../lib/packages"; | |
| 14 | 15 | import { packages } from "../../lib/services.server"; | |
| ⋯ | |||
| 221 | 222 | <span className="text-xs text-faint">Untagged</span> | |
| 222 | 223 | )} | |
| 223 | 224 | {!npm && ( | |
| 224 | − | <code className="font-mono text-xs text-muted" title={version.digest}> | |
| 225 | − | {shortDigest(version.digest)} | |
| 226 | − | </code> | |
| 225 | + | <Hint label={version.digest}> | |
| 226 | + | <code className="font-mono text-xs text-muted">{shortDigest(version.digest)}</code> | |
| 227 | + | </Hint> | |
| 227 | 228 | )} | |
| 228 | 229 | {version.deprecated && ( | |
| 229 | − | <Badge tone="neutral" title={version.deprecated}> | |
| 230 | − | Deprecated | |
| 231 | − | </Badge> | |
| 230 | + | <Hint label={version.deprecated}> | |
| 231 | + | <Badge tone="neutral">Deprecated</Badge> | |
| 232 | + | </Hint> | |
| 232 | 233 | )} | |
| 233 | 234 | {version.symbols && ( | |
| 234 | − | <Badge tone="neutral" title="A symbol package (.snupkg) was pushed: debuggers load its PDBs from the feed's symbol server."> | |
| 235 | − | Symbols | |
| 236 | − | </Badge> | |
| 235 | + | <Hint label="A symbol package (.snupkg) was pushed: debuggers load its PDBs from the feed's symbol server."> | |
| 236 | + | <Badge tone="neutral">Symbols</Badge> | |
| 237 | + | </Hint> | |
| 237 | 238 | )} | |
| 238 | 239 | </div> | |
| 239 | 240 | {version.deprecated && <p className="text-xs text-muted">{version.deprecated}</p>} | |
| ⋯ | |||
| 246 | 247 | )} | |
| 247 | 248 | {version.platforms.length > 0 && <span>{version.platforms.join(", ")}</span>} | |
| 248 | 249 | {attached.length > 0 && ( | |
| 249 | − | <span title={attached.map((a) => a.artifact_type ?? a.media_type ?? "artifact").join(", ")}> | |
| 250 | − | {attached.length} attached ({attached.map((a) => artifactWord(a)).join(", ")}) | |
| 251 | − | </span> | |
| 250 | + | <Hint label={attached.map((a) => a.artifact_type ?? a.media_type ?? "artifact").join(", ")}> | |
| 251 | + | <span> | |
| 252 | + | {attached.length} attached ({attached.map((a) => artifactWord(a)).join(", ")}) | |
| 253 | + | </span> | |
| 254 | + | </Hint> | |
| 252 | 255 | )} | |
| 253 | 256 | <span> | |
| 254 | 257 | {version.published_by ? `${version.published_by} · ` : ""} | |
| 7 | 7 | import { PackageIcon } from "../../components/package-icon"; | |
| 8 | 8 | import { CopyLine, EmptyState, TimeAgo } from "../../components/ui"; | |
| 9 | 9 | import { Badge } from "../../components/ui/badge"; | |
| 10 | + | import { Hint } from "../../components/ui/hint"; | |
| 10 | 11 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../components/ui/dropdown-menu"; | |
| 11 | 12 | import { cn } from "../../lib/cn"; | |
| 12 | 13 | import { page } from "../../lib/meta"; | |
| ⋯ | |||
| 224 | 225 | <span className="hidden shrink-0 items-center gap-5 text-xs text-muted tabular-nums sm:flex"> | |
| 225 | 226 | {pkg.latest && <span className="max-w-32 truncate font-mono text-fg-soft">{pkg.latest}</span>} | |
| 226 | 227 | <span className="w-16 text-right">{formatBytes(pkg.size)}</span> | |
| 227 | − | <span className="inline-flex w-14 items-center justify-end gap-1" title={`${pkg.downloads.toLocaleString("en-US")} downloads`}> | |
| 228 | − | <Download size={12} /> | |
| 229 | − | {shortCount(pkg.downloads)} | |
| 230 | − | </span> | |
| 228 | + | <Hint label={`${pkg.downloads.toLocaleString("en-US")} downloads`}> | |
| 229 | + | <span className="inline-flex w-14 items-center justify-end gap-1"> | |
| 230 | + | <Download size={12} /> | |
| 231 | + | {shortCount(pkg.downloads)} | |
| 232 | + | <span className="sr-only"> downloads</span> | |
| 233 | + | </span> | |
| 234 | + | </Hint> | |
| 231 | 235 | </span> | |
| 232 | 236 | </Link> | |
| 233 | 237 | </li> | |