import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
import type { Meta, StoryObj } from "@storybook/react"
import type { ReactNode } from "react"
import { intentArgType } from "../argTypes.storyHelpers.ts"
import {
StoryCell,
StoryGrid,
StoryRow,
StorySection,
} from "../board.storyHelpers.tsx"
import {
ArrowLeftIcon,
ExternalIcon,
} from "../icons.storyHelpers.tsx"
import { RouterLinkProvider } from "../RouterLink/RouterLinkProvider.tsx"
import type { RouterLinkProps } from "../RouterLink/routerLink.ts"
import { TextLink } from "./TextLink.tsx"
/**
* A stand-in for react-router's `` — the marker attribute is
* how the boards (and `TextLink.test.tsx`) show which destinations
* the injected router was handed and which ones went straight to the
* platform.
*/
const SoftRouterLink = ({
href,
...linkProps
}: RouterLinkProps): ReactNode => (
)
const INTENTS = [
"neutral",
"accent",
"success",
"warning",
"danger",
"info",
] as const
const meta = {
title: "Components/Actions/TextLink",
component: TextLink,
parameters: { layout: "padded" },
argTypes: {
intent: intentArgType,
},
// The component's own defaults, restated. Storybook has not
// seeded `args` from a docgen `defaultValue` since v7, so without
// this the props table prints `"inline"` in the Default column
// while the radio beside it has nothing selected.
args: {
appearance: "inline",
href: "/library",
intent: "accent",
isDisabled: false,
isExternal: false,
},
} satisfies Meta
export default meta
type Story = StoryObj
export const Playground: Story = {
parameters: playgroundParameters,
args: { children: "the rip queue" },
}
/**
* The two modes, in the layouts that tell them apart — an inline link
* has to be seen *inside* a sentence, and a standalone one has to be
* seen on its own line with an icon beside it.
*/
export const AllAppearances: Story = {
args: { children: "the rip queue" },
render: (linkProps) => (
Discs land in{" "}
{" "}
as soon as the drive reports them, and stay
there until a profile finishes.
}
>
Back to all discs
),
}
/**
* The back-link seven repos hand-roll, each with its own `←`, its own
* hover rule, and its own idea of whether focus is visible.
*
* The glyph is still the app's — the library ships no icons — but the
* anchor, the focus ring, and the hover are the same three everywhere
* now.
*/
export const BackLink: Story = {
args: {
appearance: "standalone",
children: "Back to all discs",
href: "/discs",
iconStart: ,
},
}
export const AllIntents: Story = {
args: { children: "the rip queue" },
render: (linkProps) => (
{INTENTS.map((intent) => (
{intent}
))}
),
}
/**
* Hover, focus, and the disabled treatment, forced by the
* pseudo-states addon rather than driven — a board that has to be
* *driven* to show its states is a test wearing a demo's clothes.
*/
export const AllStates: Story = {
args: { children: "the rip queue" },
parameters: {
pseudo: {
focusVisible: ["#focus-target"],
hover: ["#hover-inline", "#hover-standalone"],
},
},
render: (linkProps) => (
),
}
/**
* `isExternal` is three things at once: `target="_blank"`,
* `rel="noopener noreferrer"`, and a visually-hidden *"(opens in a
* new tab)"* so the surprise is announced rather than only
* discovered.
*
* The `↗` is the app's, passed as `iconEnd` — the fleet's existing
* convention, and the library ships no glyphs.
*/
export const External: Story = {
args: {
children: "the MakeMKV forum thread",
href: "https://forum.makemkv.com",
iconEnd: ,
isExternal: true,
},
}
/**
* The seam, and the three destinations it deliberately does **not**
* take.
*
* With a `RouterLinkProvider` at the root, an in-app path renders
* through the injected component and navigates softly. An external
* URL and a same-page fragment fall back to a plain `` — a router
* pushing `https://…` onto its history stack lands the SPA on a route
* that does not exist, and `#credits` is the browser's own
* scroll-to-id, not a route change.
*/
export const Routed: Story = {
args: { appearance: "standalone" },
decorators: [
(Story) => (
),
],
render: (linkProps) => (
Routed to /library
}
isExternal
>
External to makemkv.com
Fragment to #credits
),
}
export const InProse: Story = {
args: { children: "the rip queue" },
render: (linkProps) => (