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) => (

A long paragraph, set small, in which{" "} a link long enough to wrap across two lines {" "} keeps its underline on both of them and never takes the line height with it.

), }