@chesapeake/stack 0.1.2 → 0.1.4

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 CHANGED
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.1.4] - 2026-08-03
10
+
11
+ ### Added
12
+
13
+ - UX task components: `TaskInput`, `TaskCard`, and `TaskList` with
14
+ application-neutral props and public subpath exports
15
+ - UX connectivity component: `ConnectivityBadge` with tone/status presentation
16
+ - UX runner presentation: `RunnerPairingCard` and `RunnerStatusCard` for pairing
17
+ command display and runner status chrome
18
+
19
+ ## [0.1.3] - 2026-03-09
20
+
21
+ ### Changed
22
+
23
+ - `styles/index.css` now declares `@source "../components"` so Tailwind v4
24
+ detects the package's utility classes automatically. Consuming apps no longer
25
+ need to add a separate `@source` or `content` path for `@chesapeake/stack`;
26
+ `@import "@chesapeake/stack/styles"` is sufficient.
27
+
9
28
  ## [0.1.2] - 2026-03-05
10
29
 
11
30
  - `examples/` folder with copy-pasteable React examples: basic-form,
@@ -56,5 +75,8 @@ and this project adheres to
56
75
  - Full dark mode support via CSS custom properties
57
76
  - Apache 2.0 license
58
77
 
78
+ [0.1.4]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.4
79
+ [0.1.3]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.3
80
+ [0.1.2]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.2
59
81
  [0.1.1]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.1
60
82
  [0.1.0]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.0
package/README.md CHANGED
@@ -33,7 +33,10 @@ npm install react-day-picker vaul # if using calendar or drawer components
33
33
 
34
34
  ### 2. Import Styles
35
35
 
36
- Import the shared styles in your app's main CSS file (e.g., `src/index.css`):
36
+ Import the shared styles in your app's main CSS file (e.g., `src/index.css`).
37
+ The package stylesheet declares its own `@source` directives for Tailwind v4
38
+ class detection, so you do **not** need to add a separate `@source` or `content`
39
+ path pointing at `node_modules/@chesapeake/stack`.
37
40
 
38
41
  ```css
39
42
  /* Import Tailwind, third-party CSS, and shared styles */
@@ -44,48 +47,7 @@ Import the shared styles in your app's main CSS file (e.g., `src/index.css`):
44
47
  @import "@chesapeake/stack/styles/index.css";
45
48
  ```
46
49
 
47
- ### 3. Configure Tailwind
48
-
49
- Extend your `tailwind.config.ts` with the shared preset:
50
-
51
- ```typescript
52
- import type { Config } from "tailwindcss";
53
- import sharedPreset from "@chesapeake/stack/tailwind.preset";
54
-
55
- const config: Config = {
56
- content: [
57
- // your app's content paths
58
- ],
59
- presets: [sharedPreset],
60
- // your app's additional configuration
61
- };
62
-
63
- export default config;
64
- ```
65
-
66
- ### 4. Configure Tailwind Content
67
-
68
- Add the package to your Tailwind content configuration so classes are processed:
69
-
70
- **`tailwind.config.ts`**:
71
-
72
- ```typescript
73
- import type { Config } from "tailwindcss";
74
- import sharedPreset from "@chesapeake/stack/tailwind.preset";
75
-
76
- const config: Config = {
77
- content: [
78
- "./src/**/*.{js,ts,jsx,tsx}",
79
- "./node_modules/@chesapeake/stack/**/*.{js,ts,jsx,tsx}",
80
- ],
81
- presets: [sharedPreset],
82
- // your app's additional configuration
83
- };
84
-
85
- export default config;
86
- ```
87
-
88
- ### 5. Import and Use Components
50
+ ### 3. Import and Use Components
89
51
 
90
52
  Import and use components in your React components:
91
53
 
@@ -143,6 +105,10 @@ import type {
143
105
 
144
106
  ### Available Components
145
107
 
108
+ The UX task, connectivity, and runner components are application-neutral.
109
+ They accept display values and callbacks; persistence, networking, runner APIs,
110
+ and product-specific task metadata remain in the consuming application.
111
+
146
112
  #### UI Components (`components/ui/`)
147
113
 
148
114
  - **Button** - Various button variants and sizes
@@ -0,0 +1,75 @@
1
+ import type { KeyboardEvent } from "react";
2
+ import {
3
+ AlertCircle,
4
+ AlertTriangle,
5
+ Check,
6
+ CloudOff,
7
+ Loader2,
8
+ } from "lucide-react";
9
+
10
+ export type ConnectivityTone =
11
+ | "offline"
12
+ | "syncing"
13
+ | "online"
14
+ | "degraded"
15
+ | "conflict";
16
+
17
+ export interface ConnectivityBadgeProps {
18
+ label: string;
19
+ tone: ConnectivityTone;
20
+ tooltip?: string;
21
+ onAction?: () => void;
22
+ actionLabel?: string;
23
+ locked?: boolean;
24
+ className?: string;
25
+ }
26
+
27
+ export function ConnectivityBadge({
28
+ label,
29
+ tone,
30
+ tooltip,
31
+ onAction,
32
+ actionLabel,
33
+ locked = false,
34
+ className,
35
+ }: ConnectivityBadgeProps) {
36
+ const interactive = onAction != null && !locked;
37
+ const title = tooltip ?? label;
38
+ const icon =
39
+ tone === "syncing" ? (
40
+ <Loader2 className="h-4 w-4 animate-spin" />
41
+ ) : tone === "degraded" ? (
42
+ <AlertTriangle className="h-4 w-4" />
43
+ ) : tone === "conflict" ? (
44
+ <AlertCircle className="h-4 w-4" />
45
+ ) : tone === "online" ? (
46
+ <Check className="h-4 w-4" />
47
+ ) : (
48
+ <CloudOff className="h-4 w-4" />
49
+ );
50
+
51
+ return (
52
+ <div
53
+ role={interactive ? "button" : "status"}
54
+ tabIndex={interactive ? 0 : undefined}
55
+ aria-label={actionLabel ?? label}
56
+ aria-live="polite"
57
+ title={title}
58
+ className={`inline-flex h-9 items-center justify-center gap-2 rounded-md border px-3 text-sm font-medium ${tone === "online" ? "border-primary/60 text-primary bg-primary/5" : tone === "degraded" || tone === "conflict" ? "border-destructive/50 text-destructive bg-destructive/5" : "border-muted-foreground/40 text-muted-foreground bg-muted/40"} ${interactive ? "cursor-pointer" : ""} ${className ?? ""}`}
59
+ onClick={interactive ? onAction : undefined}
60
+ onKeyDown={
61
+ interactive
62
+ ? (event: KeyboardEvent) => {
63
+ if (event.key === "Enter" || event.key === " ") {
64
+ event.preventDefault();
65
+ onAction();
66
+ }
67
+ }
68
+ : undefined
69
+ }
70
+ >
71
+ {icon}
72
+ <span>{label}</span>
73
+ </div>
74
+ );
75
+ }
@@ -0,0 +1,5 @@
1
+ export { ConnectivityBadge } from "./ConnectivityBadge.tsx";
2
+ export type {
3
+ ConnectivityBadgeProps,
4
+ ConnectivityTone,
5
+ } from "./ConnectivityBadge.tsx";
@@ -44,3 +44,7 @@ export type {
44
44
  ProfilePageProps,
45
45
  StandardLayoutProps,
46
46
  } from "./StandardLayout/index.ts";
47
+
48
+ export * from "./task/index.ts";
49
+ export * from "./connectivity/index.ts";
50
+ export * from "./runner/index.ts";
@@ -0,0 +1,39 @@
1
+ import { Copy } from "lucide-react";
2
+ import { Button } from "../../ui/button.tsx";
3
+
4
+ export interface RunnerPairingCardProps {
5
+ command: string;
6
+ code?: string;
7
+ expiresAt?: string;
8
+ onCopy: () => void;
9
+ }
10
+
11
+ export function RunnerPairingCard({
12
+ command,
13
+ code,
14
+ expiresAt,
15
+ onCopy,
16
+ }: RunnerPairingCardProps) {
17
+ return (
18
+ <div className="bg-muted mt-4 space-y-2 rounded-md border p-3">
19
+ <p className="text-sm font-medium">Run this command on your device</p>
20
+ <div className="flex flex-wrap items-center gap-2">
21
+ <code className="bg-background min-w-0 flex-1 overflow-x-auto rounded border px-3 py-2 text-xs">
22
+ {command}
23
+ </code>
24
+ <Button type="button" size="sm" variant="outline" onClick={onCopy}>
25
+ <Copy className="mr-2 h-4 w-4" />
26
+ Copy
27
+ </Button>
28
+ </div>
29
+ {code && (
30
+ <p className="text-muted-foreground text-xs">
31
+ Code {code}
32
+ {expiresAt
33
+ ? ` expires ${new Date(expiresAt).toLocaleTimeString()}.`
34
+ : "."}
35
+ </p>
36
+ )}
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,57 @@
1
+ import type { ReactNode } from "react";
2
+ import { Card } from "../../ui/card.tsx";
3
+
4
+ export interface RunnerStatusCardProps {
5
+ name: string;
6
+ state: string;
7
+ details?: string;
8
+ repositories?: string[];
9
+ children?: ReactNode;
10
+ jobs?: ReactNode;
11
+ }
12
+
13
+ export function RunnerStatusCard({
14
+ name,
15
+ state,
16
+ details,
17
+ repositories = [],
18
+ children,
19
+ jobs,
20
+ }: RunnerStatusCardProps) {
21
+ return (
22
+ <Card
23
+ className="space-y-3 rounded-md border p-4"
24
+ aria-label={`${name} runner`}
25
+ >
26
+ <div className="flex flex-wrap items-start justify-between gap-3">
27
+ <div>
28
+ <div className="flex flex-wrap items-center gap-2">
29
+ <h4 className="font-medium">{name}</h4>
30
+ <span className="bg-muted rounded-full px-2 py-0.5 text-xs">
31
+ {state}
32
+ </span>
33
+ </div>
34
+ {details && (
35
+ <p className="text-muted-foreground mt-1 text-xs">{details}</p>
36
+ )}
37
+ </div>
38
+ <div className="flex flex-wrap gap-2">{children}</div>
39
+ </div>
40
+ <div>
41
+ <p className="text-sm font-medium">Repository access</p>
42
+ {repositories.length ? (
43
+ <ul className="text-muted-foreground mt-1 list-inside list-disc text-sm">
44
+ {repositories.map((repository) => (
45
+ <li key={repository}>{repository}</li>
46
+ ))}
47
+ </ul>
48
+ ) : (
49
+ <p className="text-muted-foreground mt-1 text-sm">
50
+ No repositories registered.
51
+ </p>
52
+ )}
53
+ </div>
54
+ {jobs}
55
+ </Card>
56
+ );
57
+ }
@@ -0,0 +1,4 @@
1
+ export { RunnerPairingCard } from "./RunnerPairingCard.tsx";
2
+ export type { RunnerPairingCardProps } from "./RunnerPairingCard.tsx";
3
+ export { RunnerStatusCard } from "./RunnerStatusCard.tsx";
4
+ export type { RunnerStatusCardProps } from "./RunnerStatusCard.tsx";
@@ -0,0 +1,85 @@
1
+ import { useState } from "react";
2
+ import type { ReactNode } from "react";
3
+ import { Card } from "../../ui/card.tsx";
4
+ import { Input } from "../../ui/input.tsx";
5
+
6
+ export interface TaskCardProps {
7
+ title: string;
8
+ completed: boolean;
9
+ onToggle: () => void;
10
+ onTitleChange?: (title: string) => void;
11
+ leading?: ReactNode;
12
+ content?: ReactNode;
13
+ actions?: ReactNode;
14
+ className?: string;
15
+ newlyAdded?: boolean;
16
+ }
17
+
18
+ export function TaskCard({
19
+ title,
20
+ completed,
21
+ onToggle,
22
+ onTitleChange,
23
+ leading,
24
+ content,
25
+ actions,
26
+ className,
27
+ newlyAdded = false,
28
+ }: TaskCardProps) {
29
+ const [editing, setEditing] = useState(false);
30
+ const [draft, setDraft] = useState(title);
31
+ const save = () => {
32
+ const next = draft.trim();
33
+ if (next && next !== title) onTitleChange?.(next);
34
+ setEditing(false);
35
+ };
36
+
37
+ return (
38
+ <Card
39
+ role="listitem"
40
+ className={`bg-background min-w-0 p-4 transition-all hover:shadow-md ${
41
+ completed ? "bg-muted/50 opacity-75" : ""
42
+ } ${newlyAdded ? "animate-in slide-in-from-top-2" : ""} ${className ?? ""}`}
43
+ >
44
+ <div className="flex min-w-0 items-start gap-2 md:gap-3">
45
+ {leading}
46
+ <input
47
+ type="checkbox"
48
+ checked={completed}
49
+ onChange={onToggle}
50
+ className="mt-1 h-5 w-5 shrink-0 cursor-pointer rounded text-primary focus:ring-2 focus:ring-primary"
51
+ aria-label={completed ? "Mark incomplete" : "Mark complete"}
52
+ />
53
+ <div className="min-w-0 flex-1 space-y-2">
54
+ {editing ? (
55
+ <Input
56
+ autoFocus
57
+ value={draft}
58
+ onChange={(event) => {
59
+ setDraft(event.target.value);
60
+ }}
61
+ onBlur={save}
62
+ onKeyDown={(event) => {
63
+ if (event.key === "Enter") save();
64
+ if (event.key === "Escape") setEditing(false);
65
+ }}
66
+ aria-label="Edit task title"
67
+ />
68
+ ) : (
69
+ <h3
70
+ className={`cursor-text transition-colors hover:text-primary ${completed ? "text-muted-foreground line-through" : ""}`}
71
+ onClick={() => {
72
+ setDraft(title);
73
+ if (onTitleChange) setEditing(true);
74
+ }}
75
+ >
76
+ {title}
77
+ </h3>
78
+ )}
79
+ {content}
80
+ </div>
81
+ {actions}
82
+ </div>
83
+ </Card>
84
+ );
85
+ }
@@ -0,0 +1,65 @@
1
+ import { useRef } from "react";
2
+ import type { RefObject } from "react";
3
+ import { Button } from "../../ui/button.tsx";
4
+
5
+ export interface TaskInputProps {
6
+ value: string;
7
+ onChange: (value: string) => void;
8
+ onAdd: () => void;
9
+ placeholder?: string;
10
+ inputRef?: RefObject<HTMLInputElement | null>;
11
+ className?: string;
12
+ inputClassName?: string;
13
+ addButtonLabel?: string;
14
+ /** Optional attribute for consumer product tours / E2E hooks. */
15
+ inputDataTour?: string;
16
+ }
17
+
18
+ export function TaskInput({
19
+ value,
20
+ onChange,
21
+ onAdd,
22
+ placeholder = "Add a task...",
23
+ inputRef,
24
+ className,
25
+ inputClassName,
26
+ addButtonLabel = "Add task",
27
+ inputDataTour,
28
+ }: TaskInputProps) {
29
+ const internalRef = useRef<HTMLInputElement>(null);
30
+ const ref = inputRef ?? internalRef;
31
+
32
+ return (
33
+ <div
34
+ className={`border-border bg-background border-b p-4 ${className ?? ""}`}
35
+ >
36
+ <div className="flex gap-2">
37
+ <input
38
+ ref={ref}
39
+ data-tour={inputDataTour}
40
+ className={`border-border bg-background text-foreground placeholder:text-muted-foreground flex-1 rounded border px-2 py-1 ${inputClassName ?? ""}`}
41
+ value={value}
42
+ onChange={(event) => {
43
+ onChange(event.target.value);
44
+ }}
45
+ onKeyDown={(event) => {
46
+ if (event.key === "Enter") onAdd();
47
+ }}
48
+ placeholder={placeholder}
49
+ aria-label={placeholder}
50
+ />
51
+ <Button
52
+ type="button"
53
+ variant="default"
54
+ size="icon"
55
+ onClick={onAdd}
56
+ aria-label={addButtonLabel}
57
+ >
58
+ <span aria-hidden="true" className="text-xl leading-none">
59
+ +
60
+ </span>
61
+ </Button>
62
+ </div>
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export interface TaskListProps {
4
+ children: ReactNode;
5
+ loading?: boolean;
6
+ empty?: ReactNode;
7
+ className?: string;
8
+ }
9
+
10
+ export function TaskList({
11
+ children,
12
+ loading = false,
13
+ empty,
14
+ className,
15
+ }: TaskListProps) {
16
+ const content = loading ? (
17
+ <div className="text-muted-foreground p-8 text-center" role="status">
18
+ Loading tasks...
19
+ </div>
20
+ ) : (
21
+ (empty ?? children)
22
+ );
23
+
24
+ return (
25
+ <div
26
+ className={`flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-4 ${className ?? ""}`}
27
+ role="list"
28
+ >
29
+ {content}
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,6 @@
1
+ export { TaskCard } from "./TaskCard.tsx";
2
+ export type { TaskCardProps } from "./TaskCard.tsx";
3
+ export { TaskInput } from "./TaskInput.tsx";
4
+ export type { TaskInputProps } from "./TaskInput.tsx";
5
+ export { TaskList } from "./TaskList.tsx";
6
+ export type { TaskListProps } from "./TaskList.tsx";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chesapeake/stack",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A simple, reusable React component library and design system built on shadcn/ui with Tailwind CSS",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.2.0",
@@ -107,6 +107,15 @@
107
107
  "./components/ux/auth/google/AuthProvider": "./components/ux/auth/google/AuthProvider.tsx",
108
108
  "./components/ux/auth/google/AuthCallback": "./components/ux/auth/google/AuthCallback.tsx",
109
109
  "./components/ux/auth/google/types": "./components/ux/auth/google/types.ts",
110
+ "./components/ux/task": "./components/ux/task/index.ts",
111
+ "./components/ux/task/TaskInput": "./components/ux/task/TaskInput.tsx",
112
+ "./components/ux/task/TaskCard": "./components/ux/task/TaskCard.tsx",
113
+ "./components/ux/task/TaskList": "./components/ux/task/TaskList.tsx",
114
+ "./components/ux/connectivity": "./components/ux/connectivity/index.ts",
115
+ "./components/ux/connectivity/ConnectivityBadge": "./components/ux/connectivity/ConnectivityBadge.tsx",
116
+ "./components/ux/runner": "./components/ux/runner/index.ts",
117
+ "./components/ux/runner/RunnerPairingCard": "./components/ux/runner/RunnerPairingCard.tsx",
118
+ "./components/ux/runner/RunnerStatusCard": "./components/ux/runner/RunnerStatusCard.tsx",
110
119
  "./components/ux/auth/github": "./components/ux/auth/github/index.ts",
111
120
  "./components/ux/auth/github/client": "./components/ux/auth/github/client.ts",
112
121
  "./components/ux/auth/github/default-client": "./components/ux/auth/github/default-client.ts",
package/styles/index.css CHANGED
@@ -1,5 +1,6 @@
1
1
  /* Chesapeake Shared Styles */
2
2
  @import "tailwindcss";
3
+ @source "../components";
3
4
 
4
5
  /* Design tokens and base styles */
5
6
  @import "./tokens.css";