@openbunny/react 0.0.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -0
  3. package/dist/components/chevron-icon.d.ts +4 -0
  4. package/dist/components/chevron-icon.js +6 -0
  5. package/dist/components/chevron-icon.js.map +1 -0
  6. package/dist/components/command-block.d.ts +18 -0
  7. package/dist/components/command-block.js +9 -0
  8. package/dist/components/command-block.js.map +1 -0
  9. package/dist/components/command-line.d.ts +7 -0
  10. package/dist/components/command-line.js +7 -0
  11. package/dist/components/command-line.js.map +1 -0
  12. package/dist/components/copy-button.d.ts +13 -0
  13. package/dist/components/copy-button.js +54 -0
  14. package/dist/components/copy-button.js.map +1 -0
  15. package/dist/components/page-section.d.ts +6 -0
  16. package/dist/components/page-section.js +6 -0
  17. package/dist/components/page-section.js.map +1 -0
  18. package/dist/components/page-shell.d.ts +4 -0
  19. package/dist/components/page-shell.js +5 -0
  20. package/dist/components/page-shell.js.map +1 -0
  21. package/dist/components/page-title.d.ts +7 -0
  22. package/dist/components/page-title.js +6 -0
  23. package/dist/components/page-title.js.map +1 -0
  24. package/dist/components/plate-header.d.ts +6 -0
  25. package/dist/components/plate-header.js +6 -0
  26. package/dist/components/plate-header.js.map +1 -0
  27. package/dist/components/plate.d.ts +6 -0
  28. package/dist/components/plate.js +6 -0
  29. package/dist/components/plate.js.map +1 -0
  30. package/dist/components/section-heading.d.ts +5 -0
  31. package/dist/components/section-heading.js +5 -0
  32. package/dist/components/section-heading.js.map +1 -0
  33. package/dist/components/shell-command.d.ts +4 -0
  34. package/dist/components/shell-command.js +13 -0
  35. package/dist/components/shell-command.js.map +1 -0
  36. package/dist/components/skip-link.d.ts +4 -0
  37. package/dist/components/skip-link.js +5 -0
  38. package/dist/components/skip-link.js.map +1 -0
  39. package/dist/components/status-page.d.ts +9 -0
  40. package/dist/components/status-page.js +6 -0
  41. package/dist/components/status-page.js.map +1 -0
  42. package/dist/components/ui/button.d.ts +9 -0
  43. package/dist/components/ui/button.js +23 -0
  44. package/dist/components/ui/button.js.map +1 -0
  45. package/dist/index.d.ts +19 -0
  46. package/dist/index.js +18 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/lib/command-step.d.ts +4 -0
  49. package/dist/lib/command-step.js +2 -0
  50. package/dist/lib/command-step.js.map +1 -0
  51. package/dist/lib/iso-date.d.ts +2 -0
  52. package/dist/lib/iso-date.js +20 -0
  53. package/dist/lib/iso-date.js.map +1 -0
  54. package/dist/lib/plate-asset.d.ts +6 -0
  55. package/dist/lib/plate-asset.js +2 -0
  56. package/dist/lib/plate-asset.js.map +1 -0
  57. package/dist/lib/shell-tokens.d.ts +5 -0
  58. package/dist/lib/shell-tokens.js +23 -0
  59. package/dist/lib/shell-tokens.js.map +1 -0
  60. package/dist/lib/utils.d.ts +2 -0
  61. package/dist/lib/utils.js +6 -0
  62. package/dist/lib/utils.js.map +1 -0
  63. package/dist/styles.css +22 -0
  64. package/package.json +88 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenBunny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # @openbunny/react
2
+
3
+ > **Work in progress.** Names, identifiers and interfaces can change without
4
+ > notice.
5
+
6
+ Generic React components for OpenBunny sites. The package ships components and
7
+ helpers with no site content.
8
+
9
+ ## Components
10
+
11
+ | Component | Purpose |
12
+ | --------------------------------------------- | ----------------------------------------------------- |
13
+ | `PageShell`, `PageSection` | Centred column and bordered section |
14
+ | `PageTitle`, `SectionHeading` | Page `h1` with optional date, numbered section `h2` |
15
+ | `Plate`, `PlateHeader`, `StatusPage` | Animated and static image pair, header, status layout |
16
+ | `CommandBlock`, `CommandLine`, `ShellCommand` | Numbered command steps, one command row, token spans |
17
+ | `CopyButton`, `Button` | Clipboard button, base button |
18
+ | `SkipLink`, `ChevronIcon` | Skip-to-content link, chevron icon |
19
+
20
+ The package renders no site identity. Each component takes its text, artwork
21
+ and behaviour through props. Where a prop has a default, the default is neutral
22
+ English. No component imports `next`, an analytics client or a router, and none
23
+ renders a link. A caller that needs a link passes its own element as a child or
24
+ as a prop of type `ReactNode`.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ npm install @openbunny/react @base-ui/react react react-dom
30
+ ```
31
+
32
+ `react`, `react-dom` and `@base-ui/react` are peer dependencies. Each stays a
33
+ single copy in the consumer's tree.
34
+
35
+ For a Git install, use `bun add github:openbunny/react`. `dist` is not committed;
36
+ the Git install builds it through the `prepare` script.
37
+ `bun` runs the lifecycle scripts of dependencies only for packages listed in
38
+ `trustedDependencies`, and not at all when `install.ignoreScripts` is `true`.
39
+ When the package is not listed or `install.ignoreScripts` is `true`, the install
40
+ succeeds and `dist` is missing. Add the listing to the consumer `package.json`:
41
+
42
+ ```json
43
+ { "trustedDependencies": ["@openbunny/react"] }
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```tsx
49
+ import { CommandBlock, PageShell } from "@openbunny/react"
50
+ ```
51
+
52
+ The consumer's Tailwind build must scan the package, or the utilities are not
53
+ generated, and the consumer imports the theme and the package stylesheet:
54
+
55
+ ```css
56
+ @import "tailwindcss";
57
+ @import "@openbunny/theme/css/tokens.css";
58
+ @import "@openbunny/theme/css/tailwind.css";
59
+ @import "@openbunny/theme/css/extension-base.css";
60
+ @import "@openbunny/react/styles.css";
61
+ @source "../node_modules/@openbunny/react/dist";
62
+ ```
63
+
64
+ ## Styling contract
65
+
66
+ The components style against the OpenBunny theme (`@openbunny/theme`). The
67
+ theme defines the items below; a consumer that does not use the theme defines
68
+ them itself. A missing item raises no error; the component renders unstyled in
69
+ that respect.
70
+
71
+ ### Theme custom properties and Tailwind tokens
72
+
73
+ `tailwind.css` from the theme maps each colour to a Tailwind utility. The
74
+ components use these utilities: `border-line`, `bg-paper`, `bg-paper-inset`,
75
+ `bg-foreground`, `text-background`, `text-foreground`, `text-ink`, `text-muted`,
76
+ `border-foreground`, `ring` and the `font-display`, `font-sc` and `font-mono`
77
+ families. `styles.css` reads `--ink-deep`, `--ink-mid`, `--sprout` and
78
+ `--font-weight-bold`. Components assume square corners (`--radius-*` is `0`).
79
+
80
+ ### Component classes
81
+
82
+ | Class | Defined by | Used by | Rule |
83
+ | ----------------------------------- | ----------------------------- | -------------- | ------------------------------------------------------------------- |
84
+ | `.tok-cmd`, `.tok-flag`, `.tok-str` | `@openbunny/react/styles.css` | `ShellCommand` | colours from `--ink-deep`, `--ink-mid`, `--sprout`; `.tok-cmd` bold |
85
+ | `.plate` | `@openbunny/react/styles.css` | `Plate` | `image-rendering: pixelated` |
86
+ | `.js-only` | `@openbunny/react/styles.css` | `CopyButton` | hidden under `@media (scripting: none)` |
87
+
88
+ `src/styles.test.ts` fails when a component uses a custom class that neither
89
+ this table nor the theme defines. The theme defines `.link` and `.chip`; no
90
+ component uses them.
91
+
92
+ ### Document contract
93
+
94
+ - The page has one element with `id="main"`. `SkipLink` and `StatusPage` target
95
+ it.
96
+ - `:focus-visible` has a visible outline. The theme's `extension-base.css`
97
+ provides one.
98
+
99
+ ## Props reference
100
+
101
+ Every prop is required unless its row shows a default or `?`.
102
+
103
+ | Component | Props |
104
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
105
+ | `Button` | `variant` (`"outline"`), `size` (`"xs"`), and every prop of `@base-ui/react` `Button` |
106
+ | `ChevronIcon` | `direction`: `"left"` or `"right"` |
107
+ | `CommandBlock` | `id`, `number`, `title`, `steps`: `CommandStep[]`, `note?`, `copyAll?` (`false`), `copy?`, `copyLabel?`, `lead?` |
108
+ | `CommandLine` | `command`, `copy?`, `copyLabel?` |
109
+ | `CopyButton` | `text`, `label`, `caption?`, `copiedCaption?`, `failureHint?`, `onCopy?` |
110
+ | `PageSection` | `id`, `className?`, `children` |
111
+ | `PageShell` | `children` |
112
+ | `PageTitle` | `title`, `lastChangedAt?`, `changedLabel?`, `formatDate?` |
113
+ | `Plate` | `asset`: `PlateAsset`, `className?` |
114
+ | `PlateHeader` | `asset`: `PlateAsset`, `plateClassName` |
115
+ | `SectionHeading` | `number`, `children` |
116
+ | `ShellCommand` | `command` |
117
+ | `SkipLink` | `label?` |
118
+ | `StatusPage` | `asset`: `PlateAsset`, `plateClassName`, `heading`, `children`, `actions` |
119
+
120
+ Types and helpers:
121
+
122
+ - `PlateAsset`: `gifSrc`, `staticSrc` (strings), `width`, `height` (numbers).
123
+ - `CommandStep`: `command` and `comment` (strings).
124
+ - `CopyText`: `caption?`, `copiedCaption?`, `failureHint?`.
125
+ - `shellTokens(command)` returns `ShellToken[]`; it throws on unbalanced quotes.
126
+ - `formatLongDate(isoDate)` formats an ISO date for `PageTitle`.
127
+ - `cn(...classes)` merges class names.
128
+
129
+ `bun run build` also emits declarations in `dist/index.d.ts`.
130
+
131
+ ## Props that carry text
132
+
133
+ Copy, captions and labels are props with neutral English defaults.
134
+
135
+ | Component | Props |
136
+ | -------------- | ------------------------------------------------------------------------ |
137
+ | `CopyButton` | `label` (required), `caption`, `copiedCaption`, `failureHint`, `onCopy` |
138
+ | `CommandLine` | `copy` (`caption`, `copiedCaption`, `failureHint`), `copyLabel(command)` |
139
+ | `CommandBlock` | `copy`, `copyLabel`, `copyAllCaption`, `copyAllLabel`, `note`, `lead` |
140
+ | `PageTitle` | `changedLabel`, `formatDate(isoDate)` |
141
+ | `SkipLink` | `label` |
142
+
143
+ `CopyButton` reports a successful copy through `onCopy`. The consumer attaches
144
+ analytics there; the package contains none.
145
+
146
+ ## Parity
147
+
148
+ Each component is compared with markup captured once from the component it was
149
+ extracted from. The captured files are `parity/fixtures/<component>.json`. The
150
+ package imports nothing from the original and needs no checkout of it.
151
+ `parity/fixtures/README.md` states that the fixtures are frozen and when they
152
+ change.
153
+
154
+ Package defaults differ from the original defaults in capitalisation. A case with
155
+ an `adoption` entry in `parity/definitions.tsx` lists the props that reproduce
156
+ the original markup, and the parity run also asserts that the default render
157
+ differs from the fixture. `just parity` fails when `parity/fixtures` holds no
158
+ fixture, because a gate with no cases checks nothing.
159
+
160
+ ## Development
161
+
162
+ Requires `bun` and `just`. `package.json` pins the `bun` version.
163
+
164
+ ```sh
165
+ just install
166
+ just check
167
+ ```
168
+
169
+ `just check` runs `format-check`, `lint`, `typecheck`, `test`, `build` and
170
+ `parity`, reports every failure, then exits non-zero if any failed.
171
+
172
+ The build is `tsc`, not a bundler. A bundler would collapse the module graph and
173
+ drop the `"use client"` directives that mark client components. `tsc` emits one
174
+ module per source file with those directives intact, plus declarations. The
175
+ consumer's bundler tree-shakes.
176
+
177
+ `next` is a development dependency, present only because the lint configuration extends `eslint-config-next`. The built package does not import it.
178
+
179
+ Source imports use explicit `.ts` and `.tsx` extensions. The compiler rewrites
180
+ them to `.js` in `dist`.
181
+
182
+ ## Licence
183
+
184
+ MIT. See `LICENSE`. Copyright (c) 2026 OpenBunny.
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function ChevronIcon({ direction, }: {
3
+ readonly direction: "left" | "right";
4
+ }): ReactElement;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function ChevronIcon({ direction, }) {
3
+ const d = direction === "left" ? "M10 3 5 8l5 5" : "M6 3l5 5-5 5";
4
+ return (_jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: d }) }));
5
+ }
6
+ //# sourceMappingURL=chevron-icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chevron-icon.js","sourceRoot":"","sources":["../../src/components/chevron-icon.tsx"],"names":[],"mappings":";AAEA,MAAM,UAAU,WAAW,CAAC,EAC1B,SAAS,GAGV;IACC,MAAM,CAAC,GAAG,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc,CAAA;IAEjE,OAAO,CACL,cACE,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,iBACV,MAAM,EAClB,SAAS,EAAC,OAAO,YAEjB,eAAM,CAAC,EAAE,CAAC,GAAI,GACV,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { CommandStep } from "../lib/command-step.ts";
3
+ import { type CopyText } from "./copy-button.tsx";
4
+ type CommandBlockProps = {
5
+ readonly id: string;
6
+ readonly number: string;
7
+ readonly title: string;
8
+ readonly steps: ReadonlyArray<CommandStep>;
9
+ readonly note?: string;
10
+ readonly copyAll?: boolean;
11
+ readonly copyAllCaption?: string;
12
+ readonly copyAllLabel?: string;
13
+ readonly copy?: CopyText;
14
+ readonly copyLabel?: (command: string) => string;
15
+ readonly lead?: ReactNode;
16
+ };
17
+ export declare function CommandBlock({ id, number, title, steps, note, copyAll, copyAllCaption, copyAllLabel, copy, copyLabel, lead, }: CommandBlockProps): ReactElement;
18
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CommandLine } from "./command-line.js";
3
+ import { CopyButton } from "./copy-button.js";
4
+ import { PageSection } from "./page-section.js";
5
+ import { SectionHeading } from "./section-heading.js";
6
+ export function CommandBlock({ id, number, title, steps, note, copyAll = false, copyAllCaption = "Copy all", copyAllLabel = `Copy all ${title} commands`, copy, copyLabel, lead, }) {
7
+ return (_jsxs(PageSection, { id: id, children: [_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsx(SectionHeading, { number: number, children: title }), copyAll ? (_jsx(CopyButton, { ...copy, text: steps.map((step) => step.command).join(" &&\n"), label: copyAllLabel, caption: copyAllCaption })) : null] }), lead, _jsx("ol", { className: "flex flex-col gap-4", children: steps.map((step, index) => (_jsxs("li", { className: "flex flex-col gap-1", children: [_jsxs("p", { className: "flex items-baseline gap-3 font-display text-[0.92rem] leading-[1.6]", children: [_jsxs("span", { "aria-hidden": "true", className: "font-mono text-[0.62rem] text-muted", children: [number, ".", index + 1] }), step.comment] }), _jsx(CommandLine, { command: step.command, ...(copy === undefined ? {} : { copy }), ...(copyLabel === undefined ? {} : { copyLabel }) })] }, step.command))) }), note === undefined ? null : (_jsx("p", { className: "mt-1 font-display text-[0.92rem] leading-[1.7] text-pretty text-muted", children: note }))] }));
8
+ }
9
+ //# sourceMappingURL=command-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-block.js","sourceRoot":"","sources":["../../src/components/command-block.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAoB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAiB,MAAM,kBAAmB,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAoB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAuB,CAAA;AAgBtD,MAAM,UAAU,YAAY,CAAC,EAC3B,EAAE,EACF,MAAM,EACN,KAAK,EACL,KAAK,EACL,IAAI,EACJ,OAAO,GAAG,KAAK,EACf,cAAc,GAAG,UAAU,EAC3B,YAAY,GAAG,YAAY,KAAK,WAAW,EAC3C,IAAI,EACJ,SAAS,EACT,IAAI,GACc;IAClB,OAAO,CACL,MAAC,WAAW,IAAC,EAAE,EAAE,EAAE,aACjB,eAAK,SAAS,EAAC,2CAA2C,aACxD,KAAC,cAAc,IAAC,MAAM,EAAE,MAAM,YAAG,KAAK,GAAkB,EACvD,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,UAAU,OACL,IAAI,EACR,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EACrD,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,cAAc,GACvB,CACH,CAAC,CAAC,CAAC,IAAI,IACJ,EACL,IAAI,EACL,aAAI,SAAS,EAAC,qBAAqB,YAChC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,cAAuB,SAAS,EAAC,qBAAqB,aACpD,aAAG,SAAS,EAAC,qEAAqE,aAChF,+BACc,MAAM,EAClB,SAAS,EAAC,qCAAqC,aAE9C,MAAM,OAAG,KAAK,GAAG,CAAC,IACd,EACN,IAAI,CAAC,OAAO,IACX,EACJ,KAAC,WAAW,IACV,OAAO,EAAE,IAAI,CAAC,OAAO,KACjB,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KACpC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,GAClD,KAdK,IAAI,CAAC,OAAO,CAehB,CACN,CAAC,GACC,EACJ,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC3B,YAAG,SAAS,EAAC,uEAAuE,YACjF,IAAI,GACH,CACL,IACW,CACf,CAAA;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ReactElement } from "react";
2
+ import { type CopyText } from "./copy-button.tsx";
3
+ export declare function CommandLine({ command, copy, copyLabel, }: {
4
+ readonly command: string;
5
+ readonly copy?: CopyText;
6
+ readonly copyLabel?: (command: string) => string;
7
+ }): ReactElement;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CopyButton } from "./copy-button.js";
3
+ import { ShellCommand } from "./shell-command.js";
4
+ export function CommandLine({ command, copy, copyLabel = (text) => `Copy command: ${text}`, }) {
5
+ return (_jsxs("div", { className: "flex items-center gap-3 border border-line bg-paper-inset px-4 py-2", children: [_jsxs("code", { className: "flex min-w-0 flex-1 items-baseline gap-2 font-mono text-[0.68rem] leading-[1.65] [overflow-wrap:anywhere] whitespace-break-spaces", children: [_jsx("span", { "aria-hidden": "true", className: "text-muted select-none", children: "$" }), _jsx("span", { className: "min-w-0", children: _jsx(ShellCommand, { command: command }) })] }), _jsx(CopyButton, { ...copy, text: command, label: copyLabel(command) })] }));
6
+ }
7
+ //# sourceMappingURL=command-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-line.js","sourceRoot":"","sources":["../../src/components/command-line.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAiB,MAAM,kBAAmB,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAqB,CAAA;AAElD,MAAM,UAAU,WAAW,CAAC,EAC1B,OAAO,EACP,IAAI,EACJ,SAAS,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,IAAI,EAAE,GAK9C;IACC,OAAO,CACL,eAAK,SAAS,EAAC,qEAAqE,aAClF,gBAAM,SAAS,EAAC,mIAAmI,aACjJ,8BAAkB,MAAM,EAAC,SAAS,EAAC,wBAAwB,kBAEpD,EACP,eAAM,SAAS,EAAC,SAAS,YACvB,KAAC,YAAY,IAAC,OAAO,EAAE,OAAO,GAAI,GAC7B,IACF,EACP,KAAC,UAAU,OAAK,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,GAAI,IAC9D,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ReactElement } from "react";
2
+ export type CopyText = {
3
+ readonly failureHint?: string;
4
+ readonly caption?: string;
5
+ readonly copiedCaption?: string;
6
+ };
7
+ type CopyButtonProps = CopyText & {
8
+ readonly text: string;
9
+ readonly label: string;
10
+ readonly onCopy?: () => void;
11
+ };
12
+ export declare function CopyButton({ text, label, failureHint, caption, copiedCaption, onCopy, }: CopyButtonProps): ReactElement;
13
+ export {};
@@ -0,0 +1,54 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { Button } from "./ui/button.js";
5
+ const DIM_MS = 450;
6
+ const ANNOUNCE_MS = 1600;
7
+ export function CopyButton({ text, label, failureHint = "Select the text and copy it by hand.", caption = "Copy", copiedCaption = "Copied", onCopy, }) {
8
+ const [state, setState] = useState("idle");
9
+ const [dimmed, setDimmed] = useState(false);
10
+ const resetRef = useRef(undefined);
11
+ const dimRef = useRef(undefined);
12
+ useEffect(() => {
13
+ return () => {
14
+ window.clearTimeout(resetRef.current);
15
+ window.clearTimeout(dimRef.current);
16
+ };
17
+ }, []);
18
+ async function copy() {
19
+ window.clearTimeout(resetRef.current);
20
+ window.clearTimeout(dimRef.current);
21
+ try {
22
+ await navigator.clipboard.writeText(text);
23
+ }
24
+ catch (error) {
25
+ const message = error instanceof Error ? error.message : String(error);
26
+ console.error("Copy failed:", message);
27
+ setDimmed(false);
28
+ setState("failed");
29
+ return;
30
+ }
31
+ setState("copied");
32
+ setDimmed(true);
33
+ dimRef.current = window.setTimeout(() => {
34
+ setDimmed(false);
35
+ }, DIM_MS);
36
+ resetRef.current = window.setTimeout(() => {
37
+ setState("idle");
38
+ }, ANNOUNCE_MS);
39
+ try {
40
+ onCopy?.();
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ console.error("Copy callback failed:", message);
45
+ }
46
+ }
47
+ const status = state === "failed" ? `${label} failed. ${failureHint}` : "";
48
+ return (_jsxs("span", { className: "js-only inline-flex flex-col items-end gap-1", children: [_jsx(Button, { type: "button", onClick: () => {
49
+ void copy();
50
+ }, "aria-label": label, "data-copied": dimmed ? "" : undefined, className: "border-foreground/45 bg-paper font-sc text-[0.65rem] hover:border-ink hover:bg-paper hover:text-ink", children: _jsx("span", { "aria-hidden": "true", children: dimmed ? copiedCaption : caption }) }), _jsx("span", { role: "status", className: state === "failed"
51
+ ? "max-w-64 text-right font-display text-xs leading-relaxed text-foreground/70"
52
+ : "sr-only", children: status })] }));
53
+ }
54
+ //# sourceMappingURL=copy-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-button.js","sourceRoot":"","sources":["../../src/components/copy-button.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export declare function PageSection({ id, className, children, }: {
3
+ readonly id: string;
4
+ readonly className?: string;
5
+ readonly children: ReactNode;
6
+ }): ReactElement;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ export function PageSection({ id, className, children, }) {
4
+ return (_jsx("section", { id: id, className: cn("mt-14 flex flex-col gap-3 border-t border-line pt-6", className), children: children }));
5
+ }
6
+ //# sourceMappingURL=page-section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-section.js","sourceRoot":"","sources":["../../src/components/page-section.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC,MAAM,UAAU,WAAW,CAAC,EAC1B,EAAE,EACF,SAAS,EACT,QAAQ,GAKT;IACC,OAAO,CACL,kBACE,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,EAAE,CACX,qDAAqD,EACrD,SAAS,CACV,YAEA,QAAQ,GACD,CACX,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export declare function PageShell({ children, }: {
3
+ readonly children: ReactNode;
4
+ }): ReactElement;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function PageShell({ children, }) {
3
+ return (_jsx("div", { className: "flex min-h-svh justify-center px-6 py-8 md:px-8 md:py-10", children: _jsx("div", { className: "flex w-full max-w-[65ch] min-w-0 flex-col", children: children }) }));
4
+ }
5
+ //# sourceMappingURL=page-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-shell.js","sourceRoot":"","sources":["../../src/components/page-shell.tsx"],"names":[],"mappings":";AAEA,MAAM,UAAU,SAAS,CAAC,EACxB,QAAQ,GAGT;IACC,OAAO,CACL,cAAK,SAAS,EAAC,0DAA0D,YACvE,cAAK,SAAS,EAAC,2CAA2C,YACvD,QAAQ,GACL,GACF,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function PageTitle({ title, lastChangedAt, changedLabel, formatDate, }: {
3
+ readonly title: string;
4
+ readonly lastChangedAt?: string;
5
+ readonly changedLabel?: string;
6
+ readonly formatDate?: (isoDate: string) => string;
7
+ }): ReactElement;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { formatLongDate } from "../lib/iso-date.js";
3
+ export function PageTitle({ title, lastChangedAt, changedLabel = "Last changed", formatDate = formatLongDate, }) {
4
+ return (_jsxs("div", { children: [_jsx("h1", { className: "font-display text-[1.4rem] leading-none", children: title }), lastChangedAt === undefined ? null : (_jsx("time", { dateTime: lastChangedAt, className: "mt-3 block font-mono text-[0.75rem] text-muted", children: `${changedLabel} ${formatDate(lastChangedAt.slice(0, 10))}` }))] }));
5
+ }
6
+ //# sourceMappingURL=page-title.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-title.js","sourceRoot":"","sources":["../../src/components/page-title.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEnD,MAAM,UAAU,SAAS,CAAC,EACxB,KAAK,EACL,aAAa,EACb,YAAY,GAAG,cAAc,EAC7B,UAAU,GAAG,cAAc,GAM5B;IACC,OAAO,CACL,0BACE,aAAI,SAAS,EAAC,yCAAyC,YAAE,KAAK,GAAM,EACnE,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACpC,eACE,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAC,gDAAgD,YAEzD,GAAG,YAAY,IAAI,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GACvD,CACR,IACG,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ReactElement } from "react";
2
+ import type { PlateAsset } from "../lib/plate-asset.ts";
3
+ export declare function PlateHeader({ asset, plateClassName, }: {
4
+ readonly asset: PlateAsset;
5
+ readonly plateClassName: string;
6
+ }): ReactElement;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Plate } from "./plate.js";
3
+ export function PlateHeader({ asset, plateClassName, }) {
4
+ return (_jsx("header", { className: "mt-6 border-b border-line", children: _jsx(Plate, { asset: asset, className: plateClassName }) }));
5
+ }
6
+ //# sourceMappingURL=plate-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plate-header.js","sourceRoot":"","sources":["../../src/components/plate-header.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAa,CAAA;AAEnC,MAAM,UAAU,WAAW,CAAC,EAC1B,KAAK,EACL,cAAc,GAIf;IACC,OAAO,CACL,iBAAQ,SAAS,EAAC,2BAA2B,YAC3C,KAAC,KAAK,IAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,GAAI,GAC3C,CACV,CAAA;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ReactElement } from "react";
2
+ import type { PlateAsset } from "../lib/plate-asset.ts";
3
+ export declare function Plate({ asset, className, }: {
4
+ readonly asset: PlateAsset;
5
+ readonly className?: string;
6
+ }): ReactElement;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ export function Plate({ asset, className, }) {
4
+ return (_jsxs("span", { "aria-hidden": "true", className: cn("inline-block", className), children: [_jsx("img", { src: asset.gifSrc, alt: "", width: asset.width, height: asset.height, className: "plate h-auto w-full motion-reduce:hidden" }), _jsx("img", { src: asset.staticSrc, alt: "", width: asset.width, height: asset.height, className: "plate hidden h-auto w-full motion-reduce:block" })] }));
5
+ }
6
+ //# sourceMappingURL=plate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plate.js","sourceRoot":"","sources":["../../src/components/plate.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC,MAAM,UAAU,KAAK,CAAC,EACpB,KAAK,EACL,SAAS,GAIV;IACC,OAAO,CACL,+BAAkB,MAAM,EAAC,SAAS,EAAE,EAAE,CAAC,cAAc,EAAE,SAAS,CAAC,aAC/D,cACE,GAAG,EAAE,KAAK,CAAC,MAAM,EACjB,GAAG,EAAC,EAAE,EACN,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,SAAS,EAAC,0CAA0C,GACpD,EACF,cACE,GAAG,EAAE,KAAK,CAAC,SAAS,EACpB,GAAG,EAAC,EAAE,EACN,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,SAAS,EAAC,gDAAgD,GAC1D,IACG,CACR,CAAA;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export declare function SectionHeading({ number, children, }: {
3
+ readonly number: string;
4
+ readonly children: ReactNode;
5
+ }): ReactElement;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function SectionHeading({ number, children, }) {
3
+ return (_jsxs("h2", { className: "flex items-baseline gap-3 font-sc text-[1.05rem]", children: [_jsx("span", { "aria-hidden": "true", className: "font-mono text-[0.7rem] text-muted", children: number }), children] }));
4
+ }
5
+ //# sourceMappingURL=section-heading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-heading.js","sourceRoot":"","sources":["../../src/components/section-heading.tsx"],"names":[],"mappings":";AAEA,MAAM,UAAU,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,GAIT;IACC,OAAO,CACL,cAAI,SAAS,EAAC,kDAAkD,aAC9D,8BAAkB,MAAM,EAAC,SAAS,EAAC,oCAAoC,YACpE,MAAM,GACF,EACN,QAAQ,IACN,CACN,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function ShellCommand({ command, }: {
3
+ readonly command: string;
4
+ }): ReactElement;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { shellTokens } from "../lib/shell-tokens.js";
3
+ const tokenClass = {
4
+ cmd: "tok-cmd",
5
+ flag: "tok-flag",
6
+ str: "tok-str",
7
+ text: undefined,
8
+ space: undefined,
9
+ };
10
+ export function ShellCommand({ command, }) {
11
+ return (_jsx(_Fragment, { children: shellTokens(command).map((token, index) => (_jsx("span", { className: tokenClass[token.kind], children: token.text }, index))) }));
12
+ }
13
+ //# sourceMappingURL=shell-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-command.js","sourceRoot":"","sources":["../../src/components/shell-command.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD,MAAM,UAAU,GAAG;IACjB,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,UAAU;IAChB,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;CACR,CAAA;AAEV,MAAM,UAAU,YAAY,CAAC,EAC3B,OAAO,GAGR;IACC,OAAO,CACL,4BACG,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1C,eAAkB,SAAS,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,YAChD,KAAK,CAAC,IAAI,IADF,KAAK,CAET,CACR,CAAC,GACD,CACJ,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function SkipLink({ label, }: {
3
+ readonly label?: string;
4
+ }): ReactElement;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function SkipLink({ label = "Skip to content", }) {
3
+ return (_jsx("a", { href: "#main", className: "sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:bg-foreground focus:px-3 focus:py-2 focus:text-background", children: label }));
4
+ }
5
+ //# sourceMappingURL=skip-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-link.js","sourceRoot":"","sources":["../../src/components/skip-link.tsx"],"names":[],"mappings":";AAEA,MAAM,UAAU,QAAQ,CAAC,EACvB,KAAK,GAAG,iBAAiB,GAG1B;IACC,OAAO,CACL,YACE,IAAI,EAAC,OAAO,EACZ,SAAS,EAAC,2IAA2I,YAEpJ,KAAK,GACJ,CACL,CAAA;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { PlateAsset } from "../lib/plate-asset.ts";
3
+ export declare function StatusPage({ asset, plateClassName, heading, children, actions, }: {
4
+ readonly asset: PlateAsset;
5
+ readonly plateClassName: string;
6
+ readonly heading: ReactNode;
7
+ readonly children: ReactNode;
8
+ readonly actions: ReactNode;
9
+ }): ReactElement;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Plate } from "./plate.js";
3
+ export function StatusPage({ asset, plateClassName, heading, children, actions, }) {
4
+ return (_jsx("div", { className: "flex min-h-svh items-center justify-center px-6", children: _jsxs("main", { id: "main", className: "flex max-w-[38rem] flex-col gap-6", children: [_jsx(Plate, { asset: asset, className: plateClassName }), _jsx("h1", { className: "font-display text-[1.4rem]", children: heading }), _jsx("p", { className: "font-display text-[0.92rem] leading-[1.7]", children: children }), actions] }) }));
5
+ }
6
+ //# sourceMappingURL=status-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-page.js","sourceRoot":"","sources":["../../src/components/status-page.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAa,CAAA;AAEnC,MAAM,UAAU,UAAU,CAAC,EACzB,KAAK,EACL,cAAc,EACd,OAAO,EACP,QAAQ,EACR,OAAO,GAOR;IACC,OAAO,CACL,cAAK,SAAS,EAAC,iDAAiD,YAC9D,gBAAM,EAAE,EAAC,MAAM,EAAC,SAAS,EAAC,mCAAmC,aAC3D,KAAC,KAAK,IAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,GAAI,EAClD,aAAI,SAAS,EAAC,4BAA4B,YAAE,OAAO,GAAM,EACzD,YAAG,SAAS,EAAC,2CAA2C,YAAE,QAAQ,GAAK,EACtE,OAAO,IACH,GACH,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ import type { ReactElement } from "react";
4
+ declare const buttonVariants: (props?: ({
5
+ variant?: "outline" | null | undefined;
6
+ size?: "xs" | null | undefined;
7
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
+ declare function Button({ className, variant, size, ...props }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>): ReactElement;
9
+ export { Button };
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../../lib/utils.js";
5
+ const buttonVariants = cva("inline-flex shrink-0 items-center justify-center border border-transparent bg-clip-padding text-sm leading-none font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-foreground aria-invalid:ring-3 aria-invalid:ring-foreground/20", {
6
+ variants: {
7
+ variant: {
8
+ outline: "border-border bg-background hover:bg-foreground/5 hover:text-foreground aria-expanded:bg-foreground/5 aria-expanded:text-foreground",
9
+ },
10
+ size: {
11
+ xs: "h-6 gap-1 px-2.5 text-xs",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ variant: "outline",
16
+ size: "xs",
17
+ },
18
+ });
19
+ function Button({ className, variant = "outline", size = "xs", ...props }) {
20
+ return (_jsx(ButtonPrimitive, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props }));
21
+ }
22
+ export { Button };
23
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.js","sourceRoot":"","sources":["../../../src/components/ui/button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAA;AAGjE,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC,MAAM,cAAc,GAAG,GAAG,CACxB,qYAAqY,EACrY;IACE,QAAQ,EAAE;QACR,OAAO,EAAE;YACP,OAAO,EACL,qIAAqI;SACxI;QACD,IAAI,EAAE;YACJ,EAAE,EAAE,0BAA0B;SAC/B;KACF;IACD,eAAe,EAAE;QACf,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,IAAI;KACX;CACF,CACF,CAAA;AAED,SAAS,MAAM,CAAC,EACd,SAAS,EACT,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,IAAI,EACX,GAAG,KAAK,EACoD;IAC5D,OAAO,CACL,KAAC,eAAe,iBACJ,QAAQ,EAClB,SAAS,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,KACvD,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,MAAM,EAAE,CAAA"}
@@ -0,0 +1,19 @@
1
+ export { cn } from "./lib/utils.ts";
2
+ export { shellTokens, type ShellToken } from "./lib/shell-tokens.ts";
3
+ export { formatLongDate } from "./lib/iso-date.ts";
4
+ export type { PlateAsset } from "./lib/plate-asset.ts";
5
+ export type { CommandStep } from "./lib/command-step.ts";
6
+ export { ChevronIcon } from "./components/chevron-icon.tsx";
7
+ export { PageSection } from "./components/page-section.tsx";
8
+ export { PageShell } from "./components/page-shell.tsx";
9
+ export { PageTitle } from "./components/page-title.tsx";
10
+ export { SectionHeading } from "./components/section-heading.tsx";
11
+ export { ShellCommand } from "./components/shell-command.tsx";
12
+ export { CommandLine } from "./components/command-line.tsx";
13
+ export { CommandBlock } from "./components/command-block.tsx";
14
+ export { SkipLink } from "./components/skip-link.tsx";
15
+ export { Plate } from "./components/plate.tsx";
16
+ export { PlateHeader } from "./components/plate-header.tsx";
17
+ export { StatusPage } from "./components/status-page.tsx";
18
+ export { Button } from "./components/ui/button.tsx";
19
+ export { CopyButton, type CopyText } from "./components/copy-button.tsx";
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export { cn } from "./lib/utils.js";
2
+ export { shellTokens } from "./lib/shell-tokens.js";
3
+ export { formatLongDate } from "./lib/iso-date.js";
4
+ export { ChevronIcon } from "./components/chevron-icon.js";
5
+ export { PageSection } from "./components/page-section.js";
6
+ export { PageShell } from "./components/page-shell.js";
7
+ export { PageTitle } from "./components/page-title.js";
8
+ export { SectionHeading } from "./components/section-heading.js";
9
+ export { ShellCommand } from "./components/shell-command.js";
10
+ export { CommandLine } from "./components/command-line.js";
11
+ export { CommandBlock } from "./components/command-block.js";
12
+ export { SkipLink } from "./components/skip-link.js";
13
+ export { Plate } from "./components/plate.js";
14
+ export { PlateHeader } from "./components/plate-header.js";
15
+ export { StatusPage } from "./components/status-page.js";
16
+ export { Button } from "./components/ui/button.js";
17
+ export { CopyButton } from "./components/copy-button.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACnC,OAAO,EAAE,WAAW,EAAmB,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA6B,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA6B,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAkC,CAAA;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAAgC,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAAgC,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA4B,CAAA;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAwB,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA8B,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA4B,CAAA;AACnD,OAAO,EAAE,UAAU,EAAiB,MAAM,6BAA8B,CAAA"}
@@ -0,0 +1,4 @@
1
+ export type CommandStep = {
2
+ readonly command: string;
3
+ readonly comment: string;
4
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=command-step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-step.js","sourceRoot":"","sources":["../../src/lib/command-step.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export declare function parseIsoDate(isoDate: string): Date;
2
+ export declare function formatLongDate(isoDate: string): string;
@@ -0,0 +1,20 @@
1
+ export function parseIsoDate(isoDate) {
2
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(isoDate)) {
3
+ throw new Error(`Invalid ISO date: ${isoDate}`);
4
+ }
5
+ const date = new Date(`${isoDate}T00:00:00Z`);
6
+ if (Number.isNaN(date.getTime()) ||
7
+ date.toISOString().slice(0, 10) !== isoDate) {
8
+ throw new Error(`Invalid ISO date: ${isoDate}`);
9
+ }
10
+ return date;
11
+ }
12
+ export function formatLongDate(isoDate) {
13
+ return new Intl.DateTimeFormat("en-GB", {
14
+ day: "numeric",
15
+ month: "long",
16
+ year: "numeric",
17
+ timeZone: "UTC",
18
+ }).format(parseIsoDate(isoDate));
19
+ }
20
+ //# sourceMappingURL=iso-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iso-date.js","sourceRoot":"","sources":["../../src/lib/iso-date.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAA;IACjD,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,OAAO,YAAY,CAAC,CAAA;IAC7C,IACE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,OAAO,EAC3C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAA;IACjD,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QACtC,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;AAClC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type PlateAsset = {
2
+ readonly gifSrc: string;
3
+ readonly staticSrc: string;
4
+ readonly width: number;
5
+ readonly height: number;
6
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plate-asset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plate-asset.js","sourceRoot":"","sources":["../../src/lib/plate-asset.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ export type ShellToken = {
2
+ readonly kind: "cmd" | "flag" | "str" | "text" | "space";
3
+ readonly text: string;
4
+ };
5
+ export declare function shellTokens(command: string): ReadonlyArray<ShellToken>;
@@ -0,0 +1,23 @@
1
+ const pieces = /\s+|'[^']*'|"[^"]*"|[^\s'"]+/g;
2
+ export function shellTokens(command) {
3
+ const texts = command.match(pieces) ?? [];
4
+ if (texts.join("") !== command) {
5
+ throw new Error(`Unbalanced quotes in command: ${command}`);
6
+ }
7
+ let atCommand = true;
8
+ return texts.map((text) => {
9
+ if (/^\s+$/.test(text)) {
10
+ return { kind: "space", text };
11
+ }
12
+ const kind = atCommand
13
+ ? "cmd"
14
+ : text.startsWith("'") || text.startsWith('"')
15
+ ? "str"
16
+ : /^--?[a-z]/i.test(text)
17
+ ? "flag"
18
+ : "text";
19
+ atCommand = ["|", "||", "&&", ";"].includes(text);
20
+ return { kind, text };
21
+ });
22
+ }
23
+ //# sourceMappingURL=shell-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-tokens.js","sourceRoot":"","sources":["../../src/lib/shell-tokens.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,GAAG,+BAA+B,CAAA;AAE9C,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IACzC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,EAAE,CAAC,CAAA;IAC7D,CAAC;IAED,IAAI,SAAS,GAAG,IAAI,CAAA;IACpB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAc,EAAE;QACpC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QAChC,CAAC;QAED,MAAM,IAAI,GAAG,SAAS;YACpB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAC5C,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;oBACvB,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,MAAM,CAAA;QACd,SAAS,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACjD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
@@ -0,0 +1,6 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
6
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9B,CAAC"}
@@ -0,0 +1,22 @@
1
+ .tok-cmd {
2
+ color: var(--ink-deep);
3
+ font-weight: var(--font-weight-bold);
4
+ }
5
+
6
+ .tok-flag {
7
+ color: var(--ink-mid);
8
+ }
9
+
10
+ .tok-str {
11
+ color: var(--sprout);
12
+ }
13
+
14
+ .plate {
15
+ image-rendering: pixelated;
16
+ }
17
+
18
+ @media (scripting: none) {
19
+ .js-only {
20
+ display: none;
21
+ }
22
+ }
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@openbunny/react",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Generic React components for OpenBunny sites.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/openbunny/react.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "packageManager": "bun@1.4.2",
15
+ "engines": {
16
+ "bun": "1.4.2"
17
+ },
18
+ "browserslist": [
19
+ "chrome 111",
20
+ "edge 111",
21
+ "firefox 111",
22
+ "safari 16.4"
23
+ ],
24
+ "sideEffects": [
25
+ "./dist/styles.css"
26
+ ],
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./package.json": "./package.json",
36
+ "./styles.css": "./dist/styles.css"
37
+ },
38
+ "scripts": {
39
+ "prepare": "bun run build",
40
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && cp src/styles.css dist/styles.css",
41
+ "format": "prettier --write .",
42
+ "format:check": "prettier --check .",
43
+ "lint": "eslint --max-warnings 0 .",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run --project unit --coverage",
46
+ "test:parity": "vitest run --project parity"
47
+ },
48
+ "peerDependencies": {
49
+ "@base-ui/react": "^1.7.0",
50
+ "react": "^19.3.0",
51
+ "react-dom": "^19.3.0"
52
+ },
53
+ "dependencies": {
54
+ "@types/react": "19.3.0",
55
+ "class-variance-authority": "0.7.1",
56
+ "clsx": "2.1.1",
57
+ "tailwind-merge": "3.7.0",
58
+ "typescript": "6.0.3"
59
+ },
60
+ "devDependencies": {
61
+ "@base-ui/react": "1.7.0",
62
+ "@testing-library/jest-dom": "7.0.1",
63
+ "@testing-library/react": "16.3.3",
64
+ "@types/node": "26.4.0",
65
+ "@types/react-dom": "19.3.0",
66
+ "@vitest/coverage-v8": "5.0.2",
67
+ "@vitest/eslint-plugin": "1.6.27",
68
+ "eslint": "9.39.5",
69
+ "eslint-config-next": "16.3.6",
70
+ "eslint-plugin-check-file": "3.3.2",
71
+ "eslint-plugin-compat": "7.0.2",
72
+ "eslint-plugin-promise": "7.3.0",
73
+ "eslint-plugin-regexp": "3.3.1",
74
+ "eslint-plugin-security": "4.1.0",
75
+ "eslint-plugin-unicorn": "65.0.0",
76
+ "eslint-plugin-unused-imports": "4.4.1",
77
+ "globals": "17.12.0",
78
+ "jsdom": "30.1.1",
79
+ "next": "16.3.6",
80
+ "prettier": "3.9.9",
81
+ "prettier-plugin-tailwindcss": "0.8.1",
82
+ "react": "19.3.0",
83
+ "react-dom": "19.3.0",
84
+ "tailwindcss": "4.3.3",
85
+ "typescript-eslint": "8.70.1",
86
+ "vitest": "5.0.2"
87
+ }
88
+ }