@charcuterie/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +161 -0
- package/dist/Alert/Alert.d.ts +88 -0
- package/dist/Alert/Alert.d.ts.map +1 -0
- package/dist/Alert/Alert.js +67 -0
- package/dist/Alert/Alert.js.map +1 -0
- package/dist/Badge/Badge.d.ts +54 -0
- package/dist/Badge/Badge.d.ts.map +1 -0
- package/dist/Badge/Badge.js +54 -0
- package/dist/Badge/Badge.js.map +1 -0
- package/dist/Badge/useIsTextClipped.d.ts +22 -0
- package/dist/Badge/useIsTextClipped.d.ts.map +1 -0
- package/dist/Badge/useIsTextClipped.js +57 -0
- package/dist/Badge/useIsTextClipped.js.map +1 -0
- package/dist/Button/Button.d.ts +58 -0
- package/dist/Button/Button.d.ts.map +1 -0
- package/dist/Button/Button.js +37 -0
- package/dist/Button/Button.js.map +1 -0
- package/dist/Card/Card.d.ts +44 -0
- package/dist/Card/Card.d.ts.map +1 -0
- package/dist/Card/Card.js +59 -0
- package/dist/Card/Card.js.map +1 -0
- package/dist/EmptyState/EmptyState.d.ts +42 -0
- package/dist/EmptyState/EmptyState.d.ts.map +1 -0
- package/dist/EmptyState/EmptyState.js +37 -0
- package/dist/EmptyState/EmptyState.js.map +1 -0
- package/dist/IconButton/IconButton.d.ts +34 -0
- package/dist/IconButton/IconButton.d.ts.map +1 -0
- package/dist/IconButton/IconButton.js +23 -0
- package/dist/IconButton/IconButton.js.map +1 -0
- package/dist/LiveStatusIndicator/LiveStatusIndicator.d.ts +37 -0
- package/dist/LiveStatusIndicator/LiveStatusIndicator.d.ts.map +1 -0
- package/dist/LiveStatusIndicator/LiveStatusIndicator.js +39 -0
- package/dist/LiveStatusIndicator/LiveStatusIndicator.js.map +1 -0
- package/dist/MediaTile/MediaTile.d.ts +46 -0
- package/dist/MediaTile/MediaTile.d.ts.map +1 -0
- package/dist/MediaTile/MediaTile.js +79 -0
- package/dist/MediaTile/MediaTile.js.map +1 -0
- package/dist/MediaTile/mediaStatus.d.ts +22 -0
- package/dist/MediaTile/mediaStatus.d.ts.map +1 -0
- package/dist/MediaTile/mediaStatus.js +24 -0
- package/dist/MediaTile/mediaStatus.js.map +1 -0
- package/dist/Modal/Modal.d.ts +66 -0
- package/dist/Modal/Modal.d.ts.map +1 -0
- package/dist/Modal/Modal.js +111 -0
- package/dist/Modal/Modal.js.map +1 -0
- package/dist/Modal/lockScrollBehind.d.ts +22 -0
- package/dist/Modal/lockScrollBehind.d.ts.map +1 -0
- package/dist/Modal/lockScrollBehind.js +48 -0
- package/dist/Modal/lockScrollBehind.js.map +1 -0
- package/dist/Popover/Popover.d.ts +66 -0
- package/dist/Popover/Popover.d.ts.map +1 -0
- package/dist/Popover/Popover.js +88 -0
- package/dist/Popover/Popover.js.map +1 -0
- package/dist/ProgressBar/ProgressBar.d.ts +51 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -0
- package/dist/ProgressBar/ProgressBar.js +50 -0
- package/dist/ProgressBar/ProgressBar.js.map +1 -0
- package/dist/ProgressBar/progressValue.d.ts +44 -0
- package/dist/ProgressBar/progressValue.d.ts.map +1 -0
- package/dist/ProgressBar/progressValue.js +55 -0
- package/dist/ProgressBar/progressValue.js.map +1 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +59 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -0
- package/dist/SegmentedControl/SegmentedControl.js +129 -0
- package/dist/SegmentedControl/SegmentedControl.js.map +1 -0
- package/dist/SegmentedControl/SegmentedOption.d.ts +28 -0
- package/dist/SegmentedControl/SegmentedOption.d.ts.map +1 -0
- package/dist/SegmentedControl/SegmentedOption.js +40 -0
- package/dist/SegmentedControl/SegmentedOption.js.map +1 -0
- package/dist/Skeleton/Skeleton.d.ts +30 -0
- package/dist/Skeleton/Skeleton.d.ts.map +1 -0
- package/dist/Skeleton/Skeleton.js +43 -0
- package/dist/Skeleton/Skeleton.js.map +1 -0
- package/dist/Spinner/Spinner.d.ts +37 -0
- package/dist/Spinner/Spinner.d.ts.map +1 -0
- package/dist/Spinner/Spinner.js +33 -0
- package/dist/Spinner/Spinner.js.map +1 -0
- package/dist/Tabs/TabTrigger.d.ts +32 -0
- package/dist/Tabs/TabTrigger.d.ts.map +1 -0
- package/dist/Tabs/TabTrigger.js +62 -0
- package/dist/Tabs/TabTrigger.js.map +1 -0
- package/dist/Tabs/Tabs.d.ts +95 -0
- package/dist/Tabs/Tabs.d.ts.map +1 -0
- package/dist/Tabs/Tabs.js +195 -0
- package/dist/Tabs/Tabs.js.map +1 -0
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +25 -0
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden/VisuallyHidden.js +23 -0
- package/dist/VisuallyHidden/VisuallyHidden.js.map +1 -0
- package/dist/controlStyles.d.ts +50 -0
- package/dist/controlStyles.d.ts.map +1 -0
- package/dist/controlStyles.js +60 -0
- package/dist/controlStyles.js.map +1 -0
- package/dist/expectNoAxeViolations.testHelpers.d.ts +19 -0
- package/dist/expectNoAxeViolations.testHelpers.d.ts.map +1 -0
- package/dist/expectNoAxeViolations.testHelpers.js +32 -0
- package/dist/expectNoAxeViolations.testHelpers.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/intentStyles.d.ts +76 -0
- package/dist/intentStyles.d.ts.map +1 -0
- package/dist/intentStyles.js +150 -0
- package/dist/intentStyles.js.map +1 -0
- package/dist/mountStory.testHelpers.d.ts +30 -0
- package/dist/mountStory.testHelpers.d.ts.map +1 -0
- package/dist/mountStory.testHelpers.js +19 -0
- package/dist/mountStory.testHelpers.js.map +1 -0
- package/dist/statusIntent.d.ts +33 -0
- package/dist/statusIntent.d.ts.map +1 -0
- package/dist/statusIntent.js +99 -0
- package/dist/statusIntent.js.map +1 -0
- package/dist/styles.css +130 -0
- package/dist/testing/expectAgentDrivable.d.ts +53 -0
- package/dist/testing/expectAgentDrivable.d.ts.map +1 -0
- package/dist/testing/expectAgentDrivable.js +163 -0
- package/dist/testing/expectAgentDrivable.js.map +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +10 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/toClassName.d.ts +12 -0
- package/dist/toClassName.d.ts.map +1 -0
- package/dist/toClassName.js +12 -0
- package/dist/toClassName.js.map +1 -0
- package/dist/tokens.d.ts +16 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +16 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +70 -0
- package/src/Alert/Alert.mdx +70 -0
- package/src/Alert/Alert.stories.tsx +218 -0
- package/src/Alert/Alert.test.tsx +112 -0
- package/src/Alert/Alert.tsx +166 -0
- package/src/Badge/Badge.mdx +43 -0
- package/src/Badge/Badge.stories.tsx +229 -0
- package/src/Badge/Badge.test.tsx +153 -0
- package/src/Badge/Badge.tsx +130 -0
- package/src/Badge/useIsTextClipped.ts +77 -0
- package/src/Button/Button.mdx +44 -0
- package/src/Button/Button.stories.tsx +175 -0
- package/src/Button/Button.test.tsx +114 -0
- package/src/Button/Button.tsx +125 -0
- package/src/Card/Card.mdx +53 -0
- package/src/Card/Card.stories.tsx +207 -0
- package/src/Card/Card.test.tsx +56 -0
- package/src/Card/Card.tsx +142 -0
- package/src/EmptyState/EmptyState.mdx +40 -0
- package/src/EmptyState/EmptyState.stories.tsx +183 -0
- package/src/EmptyState/EmptyState.test.tsx +41 -0
- package/src/EmptyState/EmptyState.tsx +114 -0
- package/src/IconButton/IconButton.mdx +34 -0
- package/src/IconButton/IconButton.stories.tsx +191 -0
- package/src/IconButton/IconButton.test.tsx +70 -0
- package/src/IconButton/IconButton.tsx +54 -0
- package/src/LiveStatusIndicator/LiveStatusIndicator.mdx +43 -0
- package/src/LiveStatusIndicator/LiveStatusIndicator.stories.tsx +199 -0
- package/src/LiveStatusIndicator/LiveStatusIndicator.test.tsx +63 -0
- package/src/LiveStatusIndicator/LiveStatusIndicator.tsx +116 -0
- package/src/MediaTile/MediaTile.mdx +51 -0
- package/src/MediaTile/MediaTile.stories.tsx +211 -0
- package/src/MediaTile/MediaTile.test.tsx +41 -0
- package/src/MediaTile/MediaTile.tsx +227 -0
- package/src/MediaTile/mediaStatus.ts +28 -0
- package/src/Modal/Modal.mdx +113 -0
- package/src/Modal/Modal.stories.tsx +272 -0
- package/src/Modal/Modal.test.tsx +248 -0
- package/src/Modal/Modal.tsx +228 -0
- package/src/Modal/lockScrollBehind.ts +59 -0
- package/src/Popover/Popover.mdx +102 -0
- package/src/Popover/Popover.stories.tsx +247 -0
- package/src/Popover/Popover.test.tsx +191 -0
- package/src/Popover/Popover.tsx +164 -0
- package/src/ProgressBar/ProgressBar.mdx +48 -0
- package/src/ProgressBar/ProgressBar.stories.tsx +232 -0
- package/src/ProgressBar/ProgressBar.test.tsx +74 -0
- package/src/ProgressBar/ProgressBar.tsx +172 -0
- package/src/ProgressBar/progressValue.test.ts +101 -0
- package/src/ProgressBar/progressValue.ts +87 -0
- package/src/SegmentedControl/SegmentedControl.mdx +71 -0
- package/src/SegmentedControl/SegmentedControl.stories.tsx +150 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +208 -0
- package/src/SegmentedControl/SegmentedControl.tsx +219 -0
- package/src/SegmentedControl/SegmentedOption.tsx +94 -0
- package/src/Skeleton/Skeleton.mdx +30 -0
- package/src/Skeleton/Skeleton.stories.tsx +130 -0
- package/src/Skeleton/Skeleton.test.tsx +47 -0
- package/src/Skeleton/Skeleton.tsx +106 -0
- package/src/Spinner/Spinner.mdx +36 -0
- package/src/Spinner/Spinner.stories.tsx +118 -0
- package/src/Spinner/Spinner.test.tsx +38 -0
- package/src/Spinner/Spinner.tsx +92 -0
- package/src/Tabs/TabTrigger.tsx +126 -0
- package/src/Tabs/Tabs.mdx +126 -0
- package/src/Tabs/Tabs.stories.tsx +187 -0
- package/src/Tabs/Tabs.test.tsx +283 -0
- package/src/Tabs/Tabs.tsx +333 -0
- package/src/VisuallyHidden/VisuallyHidden.mdx +29 -0
- package/src/VisuallyHidden/VisuallyHidden.stories.tsx +58 -0
- package/src/VisuallyHidden/VisuallyHidden.test.tsx +31 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +43 -0
- package/src/argTypes.storyHelpers.ts +115 -0
- package/src/board.storyHelpers.tsx +150 -0
- package/src/controlStyles.ts +79 -0
- package/src/expectNoAxeViolations.testHelpers.ts +41 -0
- package/src/icons.storyHelpers.tsx +116 -0
- package/src/index.ts +132 -0
- package/src/intentStyles.ts +207 -0
- package/src/mdxReferences.test.ts +95 -0
- package/src/mountStory.testHelpers.ts +41 -0
- package/src/sourceRules.test.ts +303 -0
- package/src/statusIntent.test.ts +136 -0
- package/src/statusIntent.ts +137 -0
- package/src/storyControls.test.ts +128 -0
- package/src/styles.css +130 -0
- package/src/tailwindCandidates.test.ts +377 -0
- package/src/testing/expectAgentDrivable.test.tsx +71 -0
- package/src/testing/expectAgentDrivable.ts +273 -0
- package/src/testing/index.ts +17 -0
- package/src/toClassName.ts +13 -0
- package/src/tokens.ts +16 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CONTROL_SIZE_CLASS, ICON_CONTROL_SIZE_CLASS, } from "../controlStyles.js";
|
|
3
|
+
import { DISABLED_CLASS, FOCUS_RING_CLASS, INTENT_APPEARANCE_CLASS, INTENT_HOVER_CLASS, } from "../intentStyles.js";
|
|
4
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
5
|
+
import { toClassName } from "../toClassName.js";
|
|
6
|
+
/**
|
|
7
|
+
* Hand-rolled in **seven repos**, each with its own
|
|
8
|
+
* primary/secondary/danger/ghost, and this is the component that
|
|
9
|
+
* erases the most duplication in the fleet.
|
|
10
|
+
*
|
|
11
|
+
* Four decisions worth knowing, all of them fixing something an
|
|
12
|
+
* inventoried repo gets wrong:
|
|
13
|
+
*
|
|
14
|
+
* - **`type="button"` by default.** A `<button>` inside a form
|
|
15
|
+
* submits it unless told otherwise, and "clicking the copy icon
|
|
16
|
+
* reloaded the page" is a bug every app in the fleet has shipped
|
|
17
|
+
* at least once.
|
|
18
|
+
* - **`isLoading` disables and announces.** The button goes
|
|
19
|
+
* `aria-busy`, the spinner replaces `iconStart` rather than
|
|
20
|
+
* being appended (so the label does not jump), and the label
|
|
21
|
+
* itself stays — a spinner that swallows the text leaves the
|
|
22
|
+
* user unsure what they pressed.
|
|
23
|
+
* - **`focus-visible`, never `focus`.** See `FOCUS_RING_CLASS`.
|
|
24
|
+
* - **Sizing comes from the density axis**, not from a prop table:
|
|
25
|
+
* `size="md"` is 2.25rem on a desktop and 3.25rem on the kiosk,
|
|
26
|
+
* with no prop change. `MIN_TOUCH_TARGET_CLASS` is available for
|
|
27
|
+
* a control that must clear 44px regardless.
|
|
28
|
+
*
|
|
29
|
+
* The accessible name is the children. `IconButton` exists because
|
|
30
|
+
* a glyph is not a name, and enforcing that in *this* component's
|
|
31
|
+
* types would mean rejecting `<Button>{icon} Copy</Button>`, which
|
|
32
|
+
* is fine.
|
|
33
|
+
*/
|
|
34
|
+
export const Button = ({ appearance = "solid", children, className, iconEnd, iconStart, intent = "accent", isDisabled = false, isFullWidth = false, isLoading = false, loadingLabel = "Loading…", size = "md", sizing = "control", type = "button", ...buttonProps }) => (_jsxs("button", { ...buttonProps, "aria-busy": isLoading || undefined, className: toClassName("inline-flex cursor-pointer items-center justify-center rounded-md border font-medium whitespace-nowrap transition-colors duration-(--duration-fast) ease-standard select-none", sizing === "icon"
|
|
35
|
+
? ICON_CONTROL_SIZE_CLASS[size]
|
|
36
|
+
: CONTROL_SIZE_CLASS[size], INTENT_APPEARANCE_CLASS[intent][appearance], INTENT_HOVER_CLASS[intent][appearance], FOCUS_RING_CLASS, DISABLED_CLASS, isFullWidth && "w-full", className), disabled: isDisabled || isLoading, type: type, children: [isLoading ? (_jsx(Spinner, { label: loadingLabel, size: size })) : (iconStart), children, iconEnd] }));
|
|
37
|
+
//# sourceMappingURL=Button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":";AASA,OAAO,EACL,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAwB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,UAAU,GAAG,OAAO,EACpB,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,EACT,MAAM,GAAG,QAAQ,EACjB,UAAU,GAAG,KAAK,EAClB,WAAW,GAAG,KAAK,EACnB,SAAS,GAAG,KAAK,EACjB,YAAY,GAAG,UAAU,EACzB,IAAI,GAAG,IAAI,EACX,MAAM,GAAG,SAAS,EAClB,IAAI,GAAG,QAAQ,EACf,GAAG,WAAW,EACF,EAAa,EAAE,CAAC,CAC5B,qBACM,WAAW,eACJ,SAAS,IAAI,SAAS,EACjC,SAAS,EAAE,WAAW,CACpB,+KAA+K,EAC/K,MAAM,KAAK,MAAM;QACf,CAAC,CAAC,uBAAuB,CAAC,IAAI,CAAC;QAC/B,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAC5B,uBAAuB,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,EAC3C,kBAAkB,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,EACtC,gBAAgB,EAChB,cAAc,EACd,WAAW,IAAI,QAAQ,EACvB,SAAS,CACV,EACD,QAAQ,EAAE,UAAU,IAAI,SAAS,EACjC,IAAI,EAAE,IAAI,aAET,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,OAAO,IAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,GAAI,CAC7C,CAAC,CAAC,CAAC,CACF,SAAS,CACV,EAEA,QAAQ,EAER,OAAO,IACD,CACV,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ElevationStep, SurfaceRole } from "@charcuterie/tokens";
|
|
2
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
export type CardSurface = Extract<SurfaceRole, "base" | "raised" | "sunken">;
|
|
4
|
+
export type CardPadding = "lg" | "md" | "none" | "sm";
|
|
5
|
+
export type CardProps = ComponentPropsWithRef<"section"> & {
|
|
6
|
+
/** Buttons or a badge, laid out beside the heading. */
|
|
7
|
+
actions?: ReactNode;
|
|
8
|
+
elevation?: ElevationStep;
|
|
9
|
+
footer?: ReactNode;
|
|
10
|
+
heading?: string;
|
|
11
|
+
headingLevel?: 2 | 3 | 4;
|
|
12
|
+
padding?: CardPadding;
|
|
13
|
+
surface?: CardSurface;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Exported only so the story can derive its `elevation` control from
|
|
17
|
+
* the same map the component indexes — `ElevationStep` comes from
|
|
18
|
+
* `@charcuterie/tokens`, which react-docgen will not follow, so the
|
|
19
|
+
* options have to be stated and this is the one statement of them
|
|
20
|
+
* that cannot drift.
|
|
21
|
+
*/
|
|
22
|
+
export declare const ELEVATION_CLASS: Record<ElevationStep, string>;
|
|
23
|
+
/**
|
|
24
|
+
* Five repos, and the reason this is a P0 rather than a nicety is
|
|
25
|
+
* the `<section>`: give a card a heading and it becomes a landmark,
|
|
26
|
+
* so `getByRole("region", { name: "Bay 3" })` scopes every
|
|
27
|
+
* subsequent query to that card. Without it, an agent driving a
|
|
28
|
+
* 16-bay tower has sixteen identical "Start" buttons and no way to
|
|
29
|
+
* say which one it means.
|
|
30
|
+
*
|
|
31
|
+
* `aria-labelledby` rather than `aria-label`, because the heading is
|
|
32
|
+
* on screen already and stating the name twice is how the two drift
|
|
33
|
+
* apart.
|
|
34
|
+
*
|
|
35
|
+
* **Density is not a prop here.** mux-magic's inventory turned up a
|
|
36
|
+
* `CardDensityProvider` in the withdrawn bambuddy evidence, and it
|
|
37
|
+
* is the wrong shape for this fleet: density is a global axis on
|
|
38
|
+
* `<html>`, so a compact bay list is `data-density="compact"` on the
|
|
39
|
+
* page, not a context around the cards. `padding` remains a prop
|
|
40
|
+
* because a card wrapping a poster grid genuinely has none, which is
|
|
41
|
+
* a layout fact rather than a density one.
|
|
42
|
+
*/
|
|
43
|
+
export declare const Card: ({ actions, children, className, elevation, footer, heading, headingLevel, padding, surface, ...sectionProps }: CardProps) => ReactNode;
|
|
44
|
+
//# sourceMappingURL=Card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,WAAW,GAAG,OAAO,CAC/B,WAAW,EACX,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAC7B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;AAErD,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,CAAC,GAAG;IACzD,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,OAAO,CAAC,EAAE,WAAW,CAAA;CACtB,CAAA;AAQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAClC,aAAa,EACb,MAAM,CAMP,CAAA;AASD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,IAAI,GAAI,+GAWlB,SAAS,KAAG,SAkDd,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useUniqueId } from "@charcuterie/logic";
|
|
3
|
+
import { toClassName } from "../toClassName.js";
|
|
4
|
+
const SURFACE_CLASS = {
|
|
5
|
+
base: "bg-surface-base",
|
|
6
|
+
raised: "bg-surface-raised",
|
|
7
|
+
sunken: "bg-surface-sunken",
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Exported only so the story can derive its `elevation` control from
|
|
11
|
+
* the same map the component indexes — `ElevationStep` comes from
|
|
12
|
+
* `@charcuterie/tokens`, which react-docgen will not follow, so the
|
|
13
|
+
* options have to be stated and this is the one statement of them
|
|
14
|
+
* that cannot drift.
|
|
15
|
+
*/
|
|
16
|
+
export const ELEVATION_CLASS = {
|
|
17
|
+
none: "shadow-none",
|
|
18
|
+
low: "shadow-low",
|
|
19
|
+
medium: "shadow-medium",
|
|
20
|
+
high: "shadow-high",
|
|
21
|
+
};
|
|
22
|
+
const PADDING_CLASS = {
|
|
23
|
+
none: "p-0",
|
|
24
|
+
sm: "p-3",
|
|
25
|
+
md: "p-4",
|
|
26
|
+
lg: "p-6",
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Five repos, and the reason this is a P0 rather than a nicety is
|
|
30
|
+
* the `<section>`: give a card a heading and it becomes a landmark,
|
|
31
|
+
* so `getByRole("region", { name: "Bay 3" })` scopes every
|
|
32
|
+
* subsequent query to that card. Without it, an agent driving a
|
|
33
|
+
* 16-bay tower has sixteen identical "Start" buttons and no way to
|
|
34
|
+
* say which one it means.
|
|
35
|
+
*
|
|
36
|
+
* `aria-labelledby` rather than `aria-label`, because the heading is
|
|
37
|
+
* on screen already and stating the name twice is how the two drift
|
|
38
|
+
* apart.
|
|
39
|
+
*
|
|
40
|
+
* **Density is not a prop here.** mux-magic's inventory turned up a
|
|
41
|
+
* `CardDensityProvider` in the withdrawn bambuddy evidence, and it
|
|
42
|
+
* is the wrong shape for this fleet: density is a global axis on
|
|
43
|
+
* `<html>`, so a compact bay list is `data-density="compact"` on the
|
|
44
|
+
* page, not a context around the cards. `padding` remains a prop
|
|
45
|
+
* because a card wrapping a poster grid genuinely has none, which is
|
|
46
|
+
* a layout fact rather than a density one.
|
|
47
|
+
*/
|
|
48
|
+
export const Card = ({ actions, children, className, elevation = "low", footer, heading, headingLevel = 2, padding = "md", surface = "raised", ...sectionProps }) => {
|
|
49
|
+
const headingId = useUniqueId();
|
|
50
|
+
const Heading = `h${headingLevel}`;
|
|
51
|
+
return (_jsxs("section", { ...sectionProps, "aria-labelledby": heading ? headingId : undefined, className: toClassName(
|
|
52
|
+
// `@container` here, not on a wrapper, is what lets the
|
|
53
|
+
// header stack at narrow widths *inside a wide viewport* —
|
|
54
|
+
// a card in a sidebar and the same card full-bleed are the
|
|
55
|
+
// case a media query cannot tell apart, and the fleet's
|
|
56
|
+
// poster grids get it wrong at intermediate widths today.
|
|
57
|
+
"@container flex flex-col gap-3 rounded-lg border border-border-subtle text-content-primary", SURFACE_CLASS[surface], ELEVATION_CLASS[elevation], PADDING_CLASS[padding], className), children: [heading || actions ? (_jsxs("header", { className: "flex flex-col gap-2 cq-sm:flex-row cq-sm:items-start cq-sm:justify-between", children: [heading ? (_jsx(Heading, { className: "font-semibold text-md leading-tight", id: headingId, children: heading })) : null, actions ? (_jsx("div", { className: "flex shrink-0 items-center gap-1", children: actions })) : null] })) : null, children, footer ? (_jsx("footer", { className: "border-t border-border-subtle pt-3 text-content-secondary text-sm", children: footer })) : null] }));
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=Card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAUhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAoB/C,MAAM,aAAa,GAAgC;IACjD,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,mBAAmB;CAC5B,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAGxB;IACF,IAAI,EAAE,aAAa;IACnB,GAAG,EAAE,YAAY;IACjB,MAAM,EAAE,eAAe;IACvB,IAAI,EAAE,aAAa;CACpB,CAAA;AAED,MAAM,aAAa,GAAgC;IACjD,IAAI,EAAE,KAAK;IACX,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;CACV,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,OAAO,EACP,QAAQ,EACR,SAAS,EACT,SAAS,GAAG,KAAK,EACjB,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,OAAO,GAAG,IAAI,EACd,OAAO,GAAG,QAAQ,EAClB,GAAG,YAAY,EACL,EAAa,EAAE;IACzB,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,sBACM,YAAY,qBACC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAChD,SAAS,EAAE,WAAW;QACpB,wDAAwD;QACxD,2DAA2D;QAC3D,2DAA2D;QAC3D,wDAAwD;QACxD,0DAA0D;QAC1D,4FAA4F,EAC5F,aAAa,CAAC,OAAO,CAAC,EACtB,eAAe,CAAC,SAAS,CAAC,EAC1B,aAAa,CAAC,OAAO,CAAC,EACtB,SAAS,CACV,aAEA,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CACpB,kBAAQ,SAAS,EAAC,4EAA4E,aAC3F,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,CACX,CAAC,CAAC,CAAC,IAAI,EAEP,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,kCAAkC,YAC9C,OAAO,GACJ,CACP,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAC,CAAC,CAAC,IAAI,EAEP,QAAQ,EAER,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,mEAAmE,YAClF,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
export type EmptyStateSize = "md" | "sm";
|
|
3
|
+
export type EmptyStateProps = Omit<ComponentPropsWithRef<"div">, "children"> & {
|
|
4
|
+
/** Buttons, usually. Rendered under the description. */
|
|
5
|
+
action?: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* A real heading element, and the level is the caller's because
|
|
9
|
+
* only the caller knows what it sits under. Getting this wrong
|
|
10
|
+
* breaks a screen reader's document outline, which is how the
|
|
11
|
+
* fleet's `<h2>`-with-a-`<button>`-inside pattern happened.
|
|
12
|
+
*/
|
|
13
|
+
headingLevel?: 2 | 3 | 4;
|
|
14
|
+
heading: string;
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
size?: EmptyStateSize;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* **Six repos, eleven-plus copies** — plex-channels alone has five
|
|
20
|
+
* `li.empty`s and castkit six `.idle`s — and not one of them is a
|
|
21
|
+
* component. Every copy is a bare string in a `<div>`, so "no
|
|
22
|
+
* results" and "not started yet" and "the socket died" all look
|
|
23
|
+
* identical, and none of them is findable by role.
|
|
24
|
+
*
|
|
25
|
+
* Designed fresh rather than ported: the two repos that had a real
|
|
26
|
+
* `EmptyState` with typed variants were bambuddy and spoolbuddy,
|
|
27
|
+
* which are upstream projects rather than Kevin's code, so that
|
|
28
|
+
* evidence was withdrawn from the plan.
|
|
29
|
+
*
|
|
30
|
+
* The shape is deliberately flat — icon, heading, description,
|
|
31
|
+
* action — with **no variant enum**. An "empty" and an "error" empty
|
|
32
|
+
* state differ in wording and in whether there is a retry button,
|
|
33
|
+
* both of which are props already; a `variant="error"` that only
|
|
34
|
+
* changes a colour invites callers to encode meaning in the colour
|
|
35
|
+
* alone, which is the one thing a colour cannot carry.
|
|
36
|
+
*
|
|
37
|
+
* The heading is what makes it agent-drivable:
|
|
38
|
+
* `getByRole("heading", { name: "No discs queued" })` works, where
|
|
39
|
+
* `getByText` against eleven copies of "Nothing here" does not.
|
|
40
|
+
*/
|
|
41
|
+
export declare const EmptyState: ({ action, className, description, heading, headingLevel, icon, size, ...divProps }: EmptyStateProps) => ReactNode;
|
|
42
|
+
//# sourceMappingURL=EmptyState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../src/EmptyState/EmptyState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAAA;AAExC,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,qBAAqB,CAAC,KAAK,CAAC,EAC5B,UAAU,CACX,GAAG;IACF,wDAAwD;IACxD,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,IAAI,CAAC,EAAE,cAAc,CAAA;CACtB,CAAA;AAUD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,UAAU,GAAI,oFASxB,eAAe,KAAG,SA6CpB,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { toClassName } from "../toClassName.js";
|
|
3
|
+
const SIZE_CLASS = {
|
|
4
|
+
sm: "gap-2 px-4 py-6 text-sm",
|
|
5
|
+
// An empty state in a 300px panel and the same one filling a
|
|
6
|
+
// dashboard want different air. Container-relative, so it is the
|
|
7
|
+
// *panel* that decides rather than the window.
|
|
8
|
+
md: "gap-3 px-6 py-10 text-md cq-md:gap-4 cq-md:px-8 cq-md:py-14",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* **Six repos, eleven-plus copies** — plex-channels alone has five
|
|
12
|
+
* `li.empty`s and castkit six `.idle`s — and not one of them is a
|
|
13
|
+
* component. Every copy is a bare string in a `<div>`, so "no
|
|
14
|
+
* results" and "not started yet" and "the socket died" all look
|
|
15
|
+
* identical, and none of them is findable by role.
|
|
16
|
+
*
|
|
17
|
+
* Designed fresh rather than ported: the two repos that had a real
|
|
18
|
+
* `EmptyState` with typed variants were bambuddy and spoolbuddy,
|
|
19
|
+
* which are upstream projects rather than Kevin's code, so that
|
|
20
|
+
* evidence was withdrawn from the plan.
|
|
21
|
+
*
|
|
22
|
+
* The shape is deliberately flat — icon, heading, description,
|
|
23
|
+
* action — with **no variant enum**. An "empty" and an "error" empty
|
|
24
|
+
* state differ in wording and in whether there is a retry button,
|
|
25
|
+
* both of which are props already; a `variant="error"` that only
|
|
26
|
+
* changes a colour invites callers to encode meaning in the colour
|
|
27
|
+
* alone, which is the one thing a colour cannot carry.
|
|
28
|
+
*
|
|
29
|
+
* The heading is what makes it agent-drivable:
|
|
30
|
+
* `getByRole("heading", { name: "No discs queued" })` works, where
|
|
31
|
+
* `getByText` against eleven copies of "Nothing here" does not.
|
|
32
|
+
*/
|
|
33
|
+
export const EmptyState = ({ action, className, description, heading, headingLevel = 2, icon, size = "md", ...divProps }) => {
|
|
34
|
+
const Heading = `h${headingLevel}`;
|
|
35
|
+
return (_jsx("div", { ...divProps, className: toClassName("@container", className), children: _jsxs("div", { className: toClassName("flex flex-col items-center justify-center text-center", SIZE_CLASS[size]), children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "text-content-muted", children: icon })) : null, _jsx(Heading, { className: "font-semibold text-content-primary", children: heading }), description ? (_jsx("p", { className: "max-w-prose text-content-secondary", children: description })) : null, action ? (_jsx("div", { className: "mt-1", children: action })) : null] }) }));
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=EmptyState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmptyState.js","sourceRoot":"","sources":["../../src/EmptyState/EmptyState.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAuB/C,MAAM,UAAU,GAAmC;IACjD,EAAE,EAAE,yBAAyB;IAC7B,6DAA6D;IAC7D,iEAAiE;IACjE,+CAA+C;IAC/C,EAAE,EAAE,6DAA6D;CAClE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,MAAM,EACN,SAAS,EACT,WAAW,EACX,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,IAAI,EACJ,IAAI,GAAG,IAAI,EACX,GAAG,QAAQ,EACK,EAAa,EAAE;IAC/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,iBACM,QAAQ,EACZ,SAAS,EAAE,WAAW,CAAC,YAAY,EAAE,SAAS,CAAC,YAQ/C,eACE,SAAS,EAAE,WAAW,CACpB,uDAAuD,EACvD,UAAU,CAAC,IAAI,CAAC,CACjB,aAEA,IAAI,CAAC,CAAC,CAAC,CACN,8BACc,MAAM,EAClB,SAAS,EAAC,oBAAoB,YAE7B,IAAI,GACA,CACR,CAAC,CAAC,CAAC,IAAI,EAER,KAAC,OAAO,IAAC,SAAS,EAAC,oCAAoC,YACpD,OAAO,GACA,EAET,WAAW,CAAC,CAAC,CAAC,CACb,YAAG,SAAS,EAAC,oCAAoC,YAC9C,WAAW,GACV,CACL,CAAC,CAAC,CAAC,IAAI,EAEP,MAAM,CAAC,CAAC,CAAC,CACR,cAAK,SAAS,EAAC,MAAM,YAAE,MAAM,GAAO,CACrC,CAAC,CAAC,CAAC,IAAI,IACJ,GACF,CACP,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ButtonProps } from "../Button/Button.tsx";
|
|
3
|
+
export type IconButtonProps = Omit<ButtonProps, "aria-label" | "children" | "iconEnd" | "iconStart" | "sizing"> & {
|
|
4
|
+
/**
|
|
5
|
+
* Required, and this single line is most of the component's
|
|
6
|
+
* reason to exist. `label` cannot be defaulted, cannot be
|
|
7
|
+
* omitted, and is not a `ReactNode` — it becomes `aria-label`,
|
|
8
|
+
* which is a string by specification.
|
|
9
|
+
*/
|
|
10
|
+
label: string;
|
|
11
|
+
/** The glyph. Anything — lucide, an inline SVG, a character. */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Three repos, three strategies, and **none of them names the
|
|
16
|
+
* button**: mux-magic hand-writes five SVG components, castkit
|
|
17
|
+
* keeps an `ICON_PATHS` map, and plex-channels renders raw glyphs
|
|
18
|
+
* (`↶`, `↷`, `▶`, `⚙`, `≡`) straight into a `<button>`. A screen
|
|
19
|
+
* reader reads the last of those as "↶", and
|
|
20
|
+
* `getByRole("button", { name: "Undo" })` finds nothing at all —
|
|
21
|
+
* which is the concrete reason the fleet is not agent-drivable
|
|
22
|
+
* today.
|
|
23
|
+
*
|
|
24
|
+
* So the type system asks for the name. There is no way to render
|
|
25
|
+
* this component without one, and `expectAgentDrivable` in the
|
|
26
|
+
* story proves the name is the one Playwright will match on.
|
|
27
|
+
*
|
|
28
|
+
* **No icons ship here.** The library does not own an icon set —
|
|
29
|
+
* lucide (ISC) is the fleet recommendation — so `children` is
|
|
30
|
+
* whatever the app already has, and the icon is `aria-hidden` by
|
|
31
|
+
* virtue of the `aria-label` overriding the subtree name.
|
|
32
|
+
*/
|
|
33
|
+
export declare const IconButton: ({ children, label, ...buttonProps }: IconButtonProps) => ReactNode;
|
|
34
|
+
//# sourceMappingURL=IconButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IconButton.d.ts","sourceRoot":"","sources":["../../src/IconButton/IconButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGvD,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,WAAW,EACT,YAAY,GACZ,UAAU,GACV,SAAS,GACT,WAAW,GACX,QAAQ,CACX,GAAG;IACF;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,GAAI,qCAIxB,eAAe,KAAG,SAMpB,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "../Button/Button.js";
|
|
3
|
+
/**
|
|
4
|
+
* Three repos, three strategies, and **none of them names the
|
|
5
|
+
* button**: mux-magic hand-writes five SVG components, castkit
|
|
6
|
+
* keeps an `ICON_PATHS` map, and plex-channels renders raw glyphs
|
|
7
|
+
* (`↶`, `↷`, `▶`, `⚙`, `≡`) straight into a `<button>`. A screen
|
|
8
|
+
* reader reads the last of those as "↶", and
|
|
9
|
+
* `getByRole("button", { name: "Undo" })` finds nothing at all —
|
|
10
|
+
* which is the concrete reason the fleet is not agent-drivable
|
|
11
|
+
* today.
|
|
12
|
+
*
|
|
13
|
+
* So the type system asks for the name. There is no way to render
|
|
14
|
+
* this component without one, and `expectAgentDrivable` in the
|
|
15
|
+
* story proves the name is the one Playwright will match on.
|
|
16
|
+
*
|
|
17
|
+
* **No icons ship here.** The library does not own an icon set —
|
|
18
|
+
* lucide (ISC) is the fleet recommendation — so `children` is
|
|
19
|
+
* whatever the app already has, and the icon is `aria-hidden` by
|
|
20
|
+
* virtue of the `aria-label` overriding the subtree name.
|
|
21
|
+
*/
|
|
22
|
+
export const IconButton = ({ children, label, ...buttonProps }) => (_jsx(Button, { ...buttonProps, "aria-label": label, sizing: "icon", children: _jsx("span", { "aria-hidden": "true", className: "contents", children: children }) }));
|
|
23
|
+
//# sourceMappingURL=IconButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IconButton.js","sourceRoot":"","sources":["../../src/IconButton/IconButton.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAqB7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,QAAQ,EACR,KAAK,EACL,GAAG,WAAW,EACE,EAAa,EAAE,CAAC,CAChC,KAAC,MAAM,OAAK,WAAW,gBAAc,KAAK,EAAE,MAAM,EAAC,MAAM,YACvD,8BAAkB,MAAM,EAAC,SAAS,EAAC,UAAU,YAC1C,QAAQ,GACJ,GACA,CACV,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ConnectionStatus } from "@charcuterie/logic";
|
|
2
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
import { DOT_SIZE_CLASS } from "../controlStyles.ts";
|
|
4
|
+
export type LiveStatusIndicatorSize = keyof typeof DOT_SIZE_CLASS;
|
|
5
|
+
export type LiveStatusIndicatorProps = Omit<ComponentPropsWithRef<"span">, "children"> & {
|
|
6
|
+
/** Extra context, announced after the label. "3 of 4 bays." */
|
|
7
|
+
detail?: string;
|
|
8
|
+
isLabelVisible?: boolean;
|
|
9
|
+
/** Overrides the machine's own wording. Rarely wanted. */
|
|
10
|
+
label?: string;
|
|
11
|
+
size?: LiveStatusIndicatorSize;
|
|
12
|
+
status: ConnectionStatus;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Four repos, four different implementations, and **it is the thing
|
|
16
|
+
* users read most** — a wall-mounted panel showing stale data with a
|
|
17
|
+
* green dot is worse than one showing nothing.
|
|
18
|
+
*
|
|
19
|
+
* The second reason it is P0: all four collapse `connecting` and
|
|
20
|
+
* `reconnecting` into one state, which reads wrong on exactly the
|
|
21
|
+
* link that needs it. A user who has seen data wants "lost it,
|
|
22
|
+
* getting it back" (amber, moving), not the cold-start spinner they
|
|
23
|
+
* already sat through (blue) and not a dead link (red). That
|
|
24
|
+
* distinction is why `connectionTransitions` is one of the two
|
|
25
|
+
* machines `@charcuterie/logic` ships rather than something each app
|
|
26
|
+
* defines, and `getConnectionIntent` / `getConnectionLabel` are
|
|
27
|
+
* exhaustive switches over it — add a fifth state and this component
|
|
28
|
+
* stops compiling instead of rendering a grey dot.
|
|
29
|
+
*
|
|
30
|
+
* `role="status"` + `aria-live="polite"` on the wrapper, so a
|
|
31
|
+
* transition is announced without interrupting whatever the user is
|
|
32
|
+
* doing. `aria-live="assertive"` would be wrong here even for
|
|
33
|
+
* `disconnected`: it cuts off the sentence in progress, and the dot
|
|
34
|
+
* is not going anywhere.
|
|
35
|
+
*/
|
|
36
|
+
export declare const LiveStatusIndicator: ({ className, detail, isLabelVisible, label, size, status, ...spanProps }: LiveStatusIndicatorProps) => ReactNode;
|
|
37
|
+
//# sourceMappingURL=LiveStatusIndicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveStatusIndicator.d.ts","sourceRoot":"","sources":["../../src/LiveStatusIndicator/LiveStatusIndicator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAapD,MAAM,MAAM,uBAAuB,GACjC,MAAM,OAAO,cAAc,CAAA;AAE7B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,qBAAqB,CAAC,MAAM,CAAC,EAC7B,UAAU,CACX,GAAG;IACF,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,uBAAuB,CAAA;IAC9B,MAAM,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,mBAAmB,GAAI,0EAQjC,wBAAwB,KAAG,SAkD7B,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DOT_SIZE_CLASS } from "../controlStyles.js";
|
|
3
|
+
import { INTENT_CONTENT_CLASS, INTENT_SOLID_FILL_CLASS, } from "../intentStyles.js";
|
|
4
|
+
import { getConnectionIntent, getConnectionLabel, getIsConnectionBusy, } from "../statusIntent.js";
|
|
5
|
+
import { toClassName } from "../toClassName.js";
|
|
6
|
+
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
7
|
+
/**
|
|
8
|
+
* Four repos, four different implementations, and **it is the thing
|
|
9
|
+
* users read most** — a wall-mounted panel showing stale data with a
|
|
10
|
+
* green dot is worse than one showing nothing.
|
|
11
|
+
*
|
|
12
|
+
* The second reason it is P0: all four collapse `connecting` and
|
|
13
|
+
* `reconnecting` into one state, which reads wrong on exactly the
|
|
14
|
+
* link that needs it. A user who has seen data wants "lost it,
|
|
15
|
+
* getting it back" (amber, moving), not the cold-start spinner they
|
|
16
|
+
* already sat through (blue) and not a dead link (red). That
|
|
17
|
+
* distinction is why `connectionTransitions` is one of the two
|
|
18
|
+
* machines `@charcuterie/logic` ships rather than something each app
|
|
19
|
+
* defines, and `getConnectionIntent` / `getConnectionLabel` are
|
|
20
|
+
* exhaustive switches over it — add a fifth state and this component
|
|
21
|
+
* stops compiling instead of rendering a grey dot.
|
|
22
|
+
*
|
|
23
|
+
* `role="status"` + `aria-live="polite"` on the wrapper, so a
|
|
24
|
+
* transition is announced without interrupting whatever the user is
|
|
25
|
+
* doing. `aria-live="assertive"` would be wrong here even for
|
|
26
|
+
* `disconnected`: it cuts off the sentence in progress, and the dot
|
|
27
|
+
* is not going anywhere.
|
|
28
|
+
*/
|
|
29
|
+
export const LiveStatusIndicator = ({ className, detail, isLabelVisible = true, label, size = "md", status, ...spanProps }) => {
|
|
30
|
+
const intent = getConnectionIntent(status);
|
|
31
|
+
const statusLabel = label ?? getConnectionLabel(status);
|
|
32
|
+
return (_jsxs("span", { ...spanProps, "aria-label": statusLabel, "aria-live": "polite", className: toClassName("inline-flex items-center gap-2 text-sm", INTENT_CONTENT_CLASS[intent], className), role: "status", "data-status": status, children: [_jsx("span", { "aria-hidden": "true", className: toClassName("inline-block shrink-0 rounded-full", DOT_SIZE_CLASS[size], INTENT_SOLID_FILL_CLASS[intent],
|
|
33
|
+
// Only the two in-flight states move. A pulsing dot that
|
|
34
|
+
// never stops is the fleet's current "is it alive?"
|
|
35
|
+
// problem in a new costume.
|
|
36
|
+
getIsConnectionBusy(status) &&
|
|
37
|
+
"charcuterie-pulse") }), isLabelVisible ? (_jsx("span", { children: statusLabel })) : (_jsx(VisuallyHidden, { children: statusLabel })), detail ? (_jsx("span", { className: "text-content-muted", children: detail })) : null] }));
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=LiveStatusIndicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveStatusIndicator.js","sourceRoot":"","sources":["../../src/LiveStatusIndicator/LiveStatusIndicator.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAsC,CAAA;AAkBrE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAClC,SAAS,EACT,MAAM,EACN,cAAc,GAAG,IAAI,EACrB,KAAK,EACL,IAAI,GAAG,IAAI,EACX,MAAM,EACN,GAAG,SAAS,EACa,EAAa,EAAE;IACxC,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;IAE1C,MAAM,WAAW,GAAG,KAAK,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAEvD,OAAO,CACL,mBACM,SAAS,gBAKD,WAAW,eACb,QAAQ,EAClB,SAAS,EAAE,WAAW,CACpB,wCAAwC,EACxC,oBAAoB,CAAC,MAAM,CAAC,EAC5B,SAAS,CACV,EACD,IAAI,EAAC,QAAQ,iBAIA,MAAM,aAEnB,8BACc,MAAM,EAClB,SAAS,EAAE,WAAW,CACpB,oCAAoC,EACpC,cAAc,CAAC,IAAI,CAAC,EACpB,uBAAuB,CAAC,MAAM,CAAC;gBAC/B,yDAAyD;gBACzD,oDAAoD;gBACpD,4BAA4B;gBAC5B,mBAAmB,CAAC,MAAM,CAAC;oBACzB,mBAAmB,CACtB,GACD,EAED,cAAc,CAAC,CAAC,CAAC,CAChB,yBAAO,WAAW,GAAQ,CAC3B,CAAC,CAAC,CAAC,CACF,KAAC,cAAc,cAAE,WAAW,GAAkB,CAC/C,EAEA,MAAM,CAAC,CAAC,CAAC,CACR,eAAM,SAAS,EAAC,oBAAoB,YAAE,MAAM,GAAQ,CACrD,CAAC,CAAC,CAAC,IAAI,IACH,CACR,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
export type MediaTileRatio = "poster" | "square" | "video";
|
|
3
|
+
export type MediaTileProps = Omit<ComponentPropsWithRef<"figure">, "children"> & {
|
|
4
|
+
/**
|
|
5
|
+
* Required. The one thing three hand-rolled poster grids all
|
|
6
|
+
* omit — and a 200-tile page of unnamed images is unnavigable
|
|
7
|
+
* rather than merely imperfect.
|
|
8
|
+
*/
|
|
9
|
+
alt: string;
|
|
10
|
+
aspectRatio?: MediaTileRatio;
|
|
11
|
+
/** A `Badge`, usually. Pinned inside the media box. */
|
|
12
|
+
badge?: ReactNode;
|
|
13
|
+
/** Shown in place of the image when it fails. */
|
|
14
|
+
fallback?: ReactNode;
|
|
15
|
+
href?: string;
|
|
16
|
+
src?: string;
|
|
17
|
+
subtitle?: ReactNode;
|
|
18
|
+
title: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Three repos, and shipping one of these is what makes
|
|
22
|
+
* plex-channels, image-viewer, and gallery-downloader look like one
|
|
23
|
+
* family instead of three.
|
|
24
|
+
*
|
|
25
|
+
* The interesting part is not the layout, it is the three states an
|
|
26
|
+
* image has and the fact that no hand-rolled version models them:
|
|
27
|
+
*
|
|
28
|
+
* - **Loading** shows a `Skeleton` at the tile's exact aspect ratio,
|
|
29
|
+
* so the grid does not reflow when 200 posters land. Layout shift
|
|
30
|
+
* is the thing users notice in these apps today.
|
|
31
|
+
* - **Error** keeps the tile's box and its name. An `<img>` with a
|
|
32
|
+
* broken `src` collapses to a glyph and takes the row's height
|
|
33
|
+
* with it; the fallback here is a `role="img"` carrying the same
|
|
34
|
+
* `alt`, so the tile stays findable and the grid stays a grid.
|
|
35
|
+
* - **Cached** is the case that bites: a `complete` image never
|
|
36
|
+
* fires `load`, so a naive `onLoad`-only implementation leaves a
|
|
37
|
+
* shimmering skeleton over a perfectly rendered poster forever.
|
|
38
|
+
* The effect below checks `complete`/`naturalWidth` on mount and
|
|
39
|
+
* on every `src` change.
|
|
40
|
+
*
|
|
41
|
+
* Those states are a `useStatus` machine rather than a
|
|
42
|
+
* `useState<string>`, which is what makes `loaded → error`
|
|
43
|
+
* impossible instead of merely unlikely — see `mediaStatus.ts`.
|
|
44
|
+
*/
|
|
45
|
+
export declare const MediaTile: ({ alt, aspectRatio, badge, className, fallback, href, src, subtitle, title, ...figureProps }: MediaTileProps) => ReactNode;
|
|
46
|
+
//# sourceMappingURL=MediaTile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaTile.d.ts","sourceRoot":"","sources":["../../src/MediaTile/MediaTile.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAOd,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE1D,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,UAAU,CACX,GAAG;IACF;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,uDAAuD;IACvD,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAUD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS,GAAI,8FAWvB,cAAc,KAAG,SAoJnB,CAAA"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useStatus } from "@charcuterie/logic";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { Skeleton } from "../Skeleton/Skeleton.js";
|
|
5
|
+
import { toClassName } from "../toClassName.js";
|
|
6
|
+
import { mediaTransitions } from "./mediaStatus.js";
|
|
7
|
+
const RATIO_CLASS = {
|
|
8
|
+
// 2:3 is the standard poster trim, which is what Plex, Kavita, and
|
|
9
|
+
// every physical sleeve in the collection actually are.
|
|
10
|
+
poster: "aspect-[2/3]",
|
|
11
|
+
square: "aspect-square",
|
|
12
|
+
video: "aspect-video",
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Three repos, and shipping one of these is what makes
|
|
16
|
+
* plex-channels, image-viewer, and gallery-downloader look like one
|
|
17
|
+
* family instead of three.
|
|
18
|
+
*
|
|
19
|
+
* The interesting part is not the layout, it is the three states an
|
|
20
|
+
* image has and the fact that no hand-rolled version models them:
|
|
21
|
+
*
|
|
22
|
+
* - **Loading** shows a `Skeleton` at the tile's exact aspect ratio,
|
|
23
|
+
* so the grid does not reflow when 200 posters land. Layout shift
|
|
24
|
+
* is the thing users notice in these apps today.
|
|
25
|
+
* - **Error** keeps the tile's box and its name. An `<img>` with a
|
|
26
|
+
* broken `src` collapses to a glyph and takes the row's height
|
|
27
|
+
* with it; the fallback here is a `role="img"` carrying the same
|
|
28
|
+
* `alt`, so the tile stays findable and the grid stays a grid.
|
|
29
|
+
* - **Cached** is the case that bites: a `complete` image never
|
|
30
|
+
* fires `load`, so a naive `onLoad`-only implementation leaves a
|
|
31
|
+
* shimmering skeleton over a perfectly rendered poster forever.
|
|
32
|
+
* The effect below checks `complete`/`naturalWidth` on mount and
|
|
33
|
+
* on every `src` change.
|
|
34
|
+
*
|
|
35
|
+
* Those states are a `useStatus` machine rather than a
|
|
36
|
+
* `useState<string>`, which is what makes `loaded → error`
|
|
37
|
+
* impossible instead of merely unlikely — see `mediaStatus.ts`.
|
|
38
|
+
*/
|
|
39
|
+
export const MediaTile = ({ alt, aspectRatio = "poster", badge, className, fallback, href, src, subtitle, title, ...figureProps }) => {
|
|
40
|
+
const imageRef = useRef(null);
|
|
41
|
+
const { can, reset, status, transitionTo } = useStatus({
|
|
42
|
+
initialState: "loading",
|
|
43
|
+
transitions: mediaTransitions,
|
|
44
|
+
});
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
// A new `src` is a new load, including after a failure.
|
|
47
|
+
reset();
|
|
48
|
+
const image = imageRef.current;
|
|
49
|
+
if (!image?.complete) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// `complete` with no intrinsic width is a *failed* cached
|
|
53
|
+
// load, not a successful one — the distinction browsers only
|
|
54
|
+
// expose this way.
|
|
55
|
+
if (image.naturalWidth > 0) {
|
|
56
|
+
if (can("loaded")) {
|
|
57
|
+
transitionTo("loaded");
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (can("error")) {
|
|
62
|
+
transitionTo("error");
|
|
63
|
+
}
|
|
64
|
+
}, [can, reset, transitionTo]);
|
|
65
|
+
const media = (_jsxs("div", { className: toClassName("relative overflow-hidden rounded-md bg-surface-sunken", RATIO_CLASS[aspectRatio]), children: [status === "error" ? (_jsx("div", { "aria-label": alt, className: "flex size-full flex-col items-center justify-center gap-1 p-2 text-center text-content-muted text-xs", role: "img", children: fallback ?? _jsx("span", { children: "Image unavailable" }) })) : (_jsxs(_Fragment, { children: [status === "loading" ? (_jsx(Skeleton, { className: "absolute inset-0 size-full" })) : null, _jsx("img", { alt: href ? "" : alt, className: toClassName("size-full object-cover transition-opacity duration-(--duration-normal) ease-standard", status === "loaded"
|
|
66
|
+
? "opacity-100"
|
|
67
|
+
: "opacity-0"), decoding: "async", loading: "lazy", onError: () => {
|
|
68
|
+
if (can("error")) {
|
|
69
|
+
transitionTo("error");
|
|
70
|
+
}
|
|
71
|
+
}, onLoad: () => {
|
|
72
|
+
if (can("loaded")) {
|
|
73
|
+
transitionTo("loaded");
|
|
74
|
+
}
|
|
75
|
+
}, ref: imageRef, src: src })] })), badge ? (_jsx("div", { className: "absolute top-2 start-2", children: badge })) : null] }));
|
|
76
|
+
const caption = (_jsxs("figcaption", { className: "flex flex-col gap-0.5 pt-2", children: [_jsx("span", { className: "truncate font-medium text-content-primary text-sm cq-sm:text-md", children: title }), subtitle ? (_jsx("span", { className: "truncate text-content-muted text-xs", children: subtitle })) : null] }));
|
|
77
|
+
return (_jsx("figure", { ...figureProps, className: toClassName("@container flex flex-col", className), children: href ? (_jsxs("a", { "aria-label": title, className: "group flex flex-col rounded-md outline-offset-2 hover:opacity-90 focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-focus-ring", href: href, children: [media, caption] })) : (_jsxs(_Fragment, { children: [media, caption] })) }));
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=MediaTile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaTile.js","sourceRoot":"","sources":["../../src/MediaTile/MediaTile.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StatusTransitions } from "@charcuterie/logic";
|
|
2
|
+
export type MediaStatus = "error" | "loaded" | "loading";
|
|
3
|
+
/**
|
|
4
|
+
* A component-owned machine, and the smallest honest example of what
|
|
5
|
+
* `createStatus` is for.
|
|
6
|
+
*
|
|
7
|
+
* `@charcuterie/logic` ships only the two machines that are
|
|
8
|
+
* genuinely fleet-wide — connection and async request — precisely so
|
|
9
|
+
* that a machine like this one lives next to the thing it describes.
|
|
10
|
+
* Three states, two of them terminal-ish, and the edges matter:
|
|
11
|
+
*
|
|
12
|
+
* - `loaded → loading` and `error → loading` exist for a changed
|
|
13
|
+
* `src`. Without them a poster grid that repaints with new URLs
|
|
14
|
+
* keeps showing the old tile's error.
|
|
15
|
+
* - `loaded → error` does **not** exist. A browser does not fire
|
|
16
|
+
* `error` on an image it has already decoded, and an app that
|
|
17
|
+
* thinks it saw that is misreading its own event wiring — so the
|
|
18
|
+
* machine throws rather than flickering a fallback over a
|
|
19
|
+
* perfectly good poster.
|
|
20
|
+
*/
|
|
21
|
+
export declare const mediaTransitions: StatusTransitions<MediaStatus>;
|
|
22
|
+
//# sourceMappingURL=mediaStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaStatus.d.ts","sourceRoot":"","sources":["../../src/MediaTile/mediaStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,CAAC,WAAW,CAKzD,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A component-owned machine, and the smallest honest example of what
|
|
3
|
+
* `createStatus` is for.
|
|
4
|
+
*
|
|
5
|
+
* `@charcuterie/logic` ships only the two machines that are
|
|
6
|
+
* genuinely fleet-wide — connection and async request — precisely so
|
|
7
|
+
* that a machine like this one lives next to the thing it describes.
|
|
8
|
+
* Three states, two of them terminal-ish, and the edges matter:
|
|
9
|
+
*
|
|
10
|
+
* - `loaded → loading` and `error → loading` exist for a changed
|
|
11
|
+
* `src`. Without them a poster grid that repaints with new URLs
|
|
12
|
+
* keeps showing the old tile's error.
|
|
13
|
+
* - `loaded → error` does **not** exist. A browser does not fire
|
|
14
|
+
* `error` on an image it has already decoded, and an app that
|
|
15
|
+
* thinks it saw that is misreading its own event wiring — so the
|
|
16
|
+
* machine throws rather than flickering a fallback over a
|
|
17
|
+
* perfectly good poster.
|
|
18
|
+
*/
|
|
19
|
+
export const mediaTransitions = {
|
|
20
|
+
error: ["loading"],
|
|
21
|
+
loaded: ["loading"],
|
|
22
|
+
loading: ["error", "loaded"],
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=mediaStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaStatus.js","sourceRoot":"","sources":["../../src/MediaTile/mediaStatus.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B;IACE,KAAK,EAAE,CAAC,SAAS,CAAC;IAClB,MAAM,EAAE,CAAC,SAAS,CAAC;IACnB,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC7B,CAAA"}
|