@effect-tui/react 0.9.0 → 0.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-tui/react",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "React bindings for @effect-tui/core",
5
5
  "type": "module",
6
6
  "files": [
@@ -83,7 +83,7 @@
83
83
  "prepublishOnly": "bun run typecheck && bun run build"
84
84
  },
85
85
  "dependencies": {
86
- "@effect-tui/core": "^0.9.0",
86
+ "@effect-tui/core": "^0.9.1",
87
87
  "@effect/platform": "^0.94.0",
88
88
  "@effect/platform-bun": "^0.87.0",
89
89
  "@effect/rpc": "^0.73.0",
package/src/dev/Toast.tsx CHANGED
@@ -2,18 +2,20 @@
2
2
  // Beautiful, minimal notifications that appear at the top of the screen
3
3
 
4
4
  import { Colors } from "@effect-tui/core"
5
- import { createContext, type ReactNode, useCallback, useContext, useState } from "react"
5
+ import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from "react"
6
6
 
7
7
  // ─────────────────────────────────────────────────────────────
8
8
  // Types
9
9
  // ─────────────────────────────────────────────────────────────
10
10
 
11
- export type ToastType = "success" | "info" | "warning" | "error"
11
+ export type ToastType = "success" | "info" | "warning" | "error" | "screenshot"
12
12
 
13
13
  export interface Toast {
14
14
  id: number
15
15
  message: string
16
16
  type: ToastType
17
+ /** Timestamp when the toast was created (for animations) */
18
+ createdAt: number
17
19
  }
18
20
 
19
21
  export interface ToastContextValue {
@@ -48,6 +50,7 @@ const TOAST_STYLES: Record<
48
50
  info: { bg: Colors.rgb(30, 50, 80), fg: Colors.rgb(140, 180, 230), icon: "ℹ" },
49
51
  warning: { bg: Colors.rgb(80, 60, 20), fg: Colors.rgb(230, 200, 100), icon: "⚠" },
50
52
  error: { bg: Colors.rgb(80, 30, 30), fg: Colors.rgb(230, 140, 140), icon: "✗" },
53
+ screenshot: { bg: Colors.rgb(30, 70, 40), fg: Colors.rgb(140, 230, 140), icon: "📷" },
51
54
  }
52
55
 
53
56
  // ─────────────────────────────────────────────────────────────
@@ -61,7 +64,7 @@ export function ToastProvider({ children }: { children: ReactNode }) {
61
64
 
62
65
  const show = useCallback((message: string, type: ToastType = "info", durationMs = 2000) => {
63
66
  const id = ++toastId
64
- setToasts((prev) => [...prev, { id, message, type }])
67
+ setToasts((prev) => [...prev, { id, message, type, createdAt: Date.now() }])
65
68
 
66
69
  setTimeout(() => {
67
70
  setToasts((prev) => prev.filter((t) => t.id !== id))
@@ -75,6 +78,65 @@ export function ToastProvider({ children }: { children: ReactNode }) {
75
78
  return <ToastContext.Provider value={{ toasts, show, dismiss }}>{children}</ToastContext.Provider>
76
79
  }
77
80
 
81
+ // ─────────────────────────────────────────────────────────────
82
+ // Screenshot Toast Animation
83
+ // ─────────────────────────────────────────────────────────────
84
+
85
+ // Animation phases: camera → flash → success
86
+ type ScreenshotPhase = "camera" | "flash" | "success"
87
+
88
+ const SCREENSHOT_PHASES: Record<
89
+ ScreenshotPhase,
90
+ { icon: string; bg: ReturnType<typeof Colors.rgb>; fg: ReturnType<typeof Colors.rgb> }
91
+ > = {
92
+ camera: {
93
+ icon: "📷",
94
+ bg: Colors.rgb(40, 50, 60),
95
+ fg: Colors.rgb(180, 200, 220),
96
+ },
97
+ flash: {
98
+ icon: "⚡",
99
+ bg: Colors.rgb(255, 255, 200), // Bright flash!
100
+ fg: Colors.rgb(60, 60, 40),
101
+ },
102
+ success: {
103
+ icon: "✓",
104
+ bg: Colors.rgb(30, 70, 40),
105
+ fg: Colors.rgb(140, 230, 140),
106
+ },
107
+ }
108
+
109
+ function ScreenshotToast({ message, createdAt }: { message: string; createdAt: number }) {
110
+ const [phase, setPhase] = useState<ScreenshotPhase>("camera")
111
+
112
+ useEffect(() => {
113
+ // Phase timing: camera (0-120ms) → flash (120-280ms) → success (280ms+)
114
+ const elapsed = Date.now() - createdAt
115
+ const cameraDelay = Math.max(0, 120 - elapsed)
116
+ const flashDelay = Math.max(0, 280 - elapsed)
117
+
118
+ const flashTimer = setTimeout(() => setPhase("flash"), cameraDelay)
119
+ const successTimer = setTimeout(() => setPhase("success"), flashDelay)
120
+
121
+ return () => {
122
+ clearTimeout(flashTimer)
123
+ clearTimeout(successTimer)
124
+ }
125
+ }, [createdAt])
126
+
127
+ const style = SCREENSHOT_PHASES[phase]
128
+ const content = ` ${style.icon} ${message} `
129
+
130
+ return (
131
+ <hstack>
132
+ <spacer />
133
+ <box bg={style.bg} padding={{ x: 1 }}>
134
+ <text fg={style.fg}>{content}</text>
135
+ </box>
136
+ </hstack>
137
+ )
138
+ }
139
+
78
140
  // ─────────────────────────────────────────────────────────────
79
141
  // Toast Display Component
80
142
  // ─────────────────────────────────────────────────────────────
@@ -86,6 +148,12 @@ export function ToastContainer() {
86
148
 
87
149
  // Show only the most recent toast
88
150
  const toast = toasts[toasts.length - 1]
151
+
152
+ // Special animated toast for screenshots
153
+ if (toast.type === "screenshot") {
154
+ return <ScreenshotToast message={toast.message} createdAt={toast.createdAt} />
155
+ }
156
+
89
157
  const style = TOAST_STYLES[toast.type]
90
158
 
91
159
  // Compact pill in upper right