feat(design): every button on its family, the chip primitive, the lint rule (E28 S3b2)
The second half of the button sweep: the remaining hand-rolled buttons in
feature components, the design lab and the components/ui composites now
stand on Button, IconButton, Pressable, Chip or DragHandle.
- Chip + DismissibleChip (filter / token / tag / suggest / ghost, 24px
floor, 44 on touch) replace five chip geometries; FilterChipGroup
renders Chips; overflow lanes measure a mirror painted by chipClass.
- The Plex control row is IconButton's player rung (36, 20px glyph,
round, overlay; 44 on touch); the YT modal bar is on the sm rung with
pressed cycle toggles and names that survive a folded label.
- New variants: IconButton ghost-accent, danger-soft; Button warn on a
new state-warn utility. The .btn-{tone} recipes and btnCls are gone.
- Fixed on the way: membership rows (collection editor, Plex playlist
add) announce checked/mixed; a one-letter tag chip was 21px wide;
descriptionLinks moved out of lib/ (it renders components).
- local/button-primitive: a raw <button>/<FocusSafeButton> outside the
control-primitive files is a lint error (contract-tested, seen red).
This commit is contained in:
@@ -11,6 +11,7 @@ import Toaster from "./ui/Toaster";
|
||||
import { useOverlayOrder } from "./ui/overlayOrder";
|
||||
import { useLayer } from "../lib/layerStack";
|
||||
import { trapTab, useDialogChrome } from "./ui/dialogFocus";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
|
||||
// A first-login wizard that walks the user through granting YouTube access one scope at a
|
||||
// time, each with a plain explanation and an up-front heads-up about Google's "unverified
|
||||
@@ -172,13 +173,14 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
|
||||
aria-labelledby={titleId}
|
||||
className="glass relative w-[min(94vw,460px)] rounded-2xl p-7 text-center outline-none animate-[panelIn_0.26s_cubic-bezier(0.22,1,0.36,1)]"
|
||||
>
|
||||
<button
|
||||
<IconButton
|
||||
size="md"
|
||||
onClick={close}
|
||||
className="absolute top-3 right-3 p-2 rounded-lg text-muted hover:text-fg hover:bg-card/60 transition"
|
||||
aria-label={t("onboarding.close")}
|
||||
className="absolute top-3 right-3"
|
||||
label={t("onboarding.close")}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
<X />
|
||||
</IconButton>
|
||||
|
||||
{step === "read" && (
|
||||
<>
|
||||
@@ -196,12 +198,9 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
|
||||
</p>
|
||||
<ConsentHeadsUp />
|
||||
<div className="mt-5 flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => beginGrant("read")}
|
||||
className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold btn-primary transition"
|
||||
>
|
||||
<Youtube className="w-4 h-4" /> {t("onboarding.read.connect")}
|
||||
</button>
|
||||
<Button size="lg" icon={<Youtube />} onClick={() => beginGrant("read")}>
|
||||
{t("onboarding.read.connect")}
|
||||
</Button>
|
||||
<Button variant="ghost" size="md" onClick={close}>
|
||||
{t("onboarding.read.skip")}
|
||||
</Button>
|
||||
@@ -254,21 +253,21 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
|
||||
)}
|
||||
</p>
|
||||
{importSubs.isError && (
|
||||
<button
|
||||
<Button
|
||||
variant="outline"
|
||||
icon={<RefreshCw />}
|
||||
loading={importSubs.isPending}
|
||||
onClick={() => importSubs.mutate()}
|
||||
className="mb-3 inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl text-sm bg-card border border-border hover:border-accent transition"
|
||||
className="mb-3"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" /> {t("onboarding.write.retryImport")}
|
||||
</button>
|
||||
{t("onboarding.write.retryImport")}
|
||||
</Button>
|
||||
)}
|
||||
<ConsentHeadsUp />
|
||||
<div className="mt-5 flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => beginGrant("write")}
|
||||
className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold btn-primary transition"
|
||||
>
|
||||
<ShieldCheck className="w-4 h-4" /> {t("onboarding.write.enableUnsubscribe")}
|
||||
</button>
|
||||
<Button size="lg" icon={<ShieldCheck />} onClick={() => beginGrant("write")}>
|
||||
{t("onboarding.write.enableUnsubscribe")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="md"
|
||||
@@ -294,15 +293,16 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
|
||||
<p className="text-sm text-muted leading-relaxed mt-2 mb-5">
|
||||
{t("onboarding.done.body")}
|
||||
</p>
|
||||
<button
|
||||
<Button
|
||||
size="lg"
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
endOnboarding(false);
|
||||
onClose();
|
||||
}}
|
||||
className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold btn-primary transition w-full"
|
||||
>
|
||||
{t("onboarding.done.done")}
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user