@playfast/reform-better-hub-ui-primitives 0.2.1 → 0.2.2
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 +1 -1
- package/src/components/agent-icon.tsx +0 -3
- package/src/components/autocomplete.tsx +189 -0
- package/src/components/badge.tsx +0 -2
- package/src/components/button.tsx +0 -2
- package/src/components/combobox.tsx +0 -3
- package/src/components/command.tsx +0 -3
- package/src/components/dialog.tsx +0 -3
- package/src/components/dropdown-menu.tsx +0 -3
- package/src/components/github-background.tsx +0 -13
- package/src/components/halftone-background.tsx +1 -22
- package/src/components/halftone-config.ts +14 -0
- package/src/components/input-group.tsx +0 -9
- package/src/components/input.tsx +0 -3
- package/src/components/live-duration.tsx +1 -8
- package/src/components/logo.tsx +0 -2
- package/src/components/popover.tsx +0 -3
- package/src/components/resize-handle.tsx +0 -12
- package/src/components/sheet.tsx +0 -3
- package/src/components/textarea.tsx +0 -2
- package/src/components/time-ago.tsx +0 -4
- package/src/components/tooltip.tsx +0 -3
- package/src/foundation/cn.ts +0 -4
- package/src/index.ts +1 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-better-hub-ui-primitives",
|
|
3
3
|
"playbook": "./playbook",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "React UI primitives adapted from better-hub by Better Auth (https://github.com/better-auth/better-hub). Full credit to the Better Auth team.",
|
|
7
7
|
"keywords": [
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { String as Str } from 'effect'
|
|
3
|
+
import { CheckIcon, ChevronDownIcon, Loader2Icon } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../foundation/cn'
|
|
6
|
+
import { Input } from './input'
|
|
7
|
+
|
|
8
|
+
export interface AutocompleteItemExternalApi {
|
|
9
|
+
readonly value: string
|
|
10
|
+
readonly label?: string
|
|
11
|
+
readonly description?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AutocompletePropsExternalApi {
|
|
15
|
+
readonly value: string
|
|
16
|
+
readonly onValueChange: (next: string) => void
|
|
17
|
+
readonly items: ReadonlyArray<AutocompleteItemExternalApi>
|
|
18
|
+
readonly placeholder?: string
|
|
19
|
+
readonly 'aria-label': string
|
|
20
|
+
readonly loading?: boolean
|
|
21
|
+
readonly loadingText?: string
|
|
22
|
+
readonly emptyText?: string
|
|
23
|
+
readonly disabled?: boolean
|
|
24
|
+
readonly className?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// blur close is delayed so suggestion mousedown (which blurs the input) still lands
|
|
28
|
+
const BLUR_CLOSE_DELAY_MS = 120
|
|
29
|
+
|
|
30
|
+
const matchesQuery = (suggestion: AutocompleteItemExternalApi, needle: string): boolean =>
|
|
31
|
+
[suggestion.value, suggestion.label, suggestion.description].some(
|
|
32
|
+
(text) => text !== undefined && Str.toLowerCase(text).includes(needle),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export function Autocomplete({
|
|
36
|
+
value: fieldValue,
|
|
37
|
+
onValueChange,
|
|
38
|
+
items: suggestions,
|
|
39
|
+
placeholder,
|
|
40
|
+
'aria-label': ariaLabel,
|
|
41
|
+
loading = false,
|
|
42
|
+
loadingText = 'Loading…',
|
|
43
|
+
emptyText = 'No matches — the typed value is used as-is.',
|
|
44
|
+
disabled = false,
|
|
45
|
+
className,
|
|
46
|
+
}: AutocompletePropsExternalApi): React.JSX.Element {
|
|
47
|
+
const [open, setOpen] = React.useState(false)
|
|
48
|
+
// filter only what was typed since open (empty right after open ⇒ full list)
|
|
49
|
+
const [query, setQuery] = React.useState('')
|
|
50
|
+
const [highlighted, setHighlighted] = React.useState(-1)
|
|
51
|
+
const listId = React.useId()
|
|
52
|
+
|
|
53
|
+
const needle = Str.toLowerCase(query)
|
|
54
|
+
const filtered =
|
|
55
|
+
query === '' ? suggestions : suggestions.filter((suggestion) => matchesQuery(suggestion, needle))
|
|
56
|
+
const activeIndex = highlighted >= 0 && highlighted < filtered.length ? highlighted : -1
|
|
57
|
+
const optionId = (index: number): string => `${listId}-option-${index}`
|
|
58
|
+
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
if (activeIndex >= 0) {
|
|
61
|
+
document.getElementById(optionId(activeIndex))?.scrollIntoView({ block: 'nearest' })
|
|
62
|
+
}
|
|
63
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps -- optionId is stable per listId
|
|
64
|
+
}, [activeIndex])
|
|
65
|
+
|
|
66
|
+
const openList = (): void => {
|
|
67
|
+
setQuery('')
|
|
68
|
+
setHighlighted(-1)
|
|
69
|
+
setOpen(true)
|
|
70
|
+
}
|
|
71
|
+
const pick = (suggestion: AutocompleteItemExternalApi): void => {
|
|
72
|
+
onValueChange(suggestion.value)
|
|
73
|
+
setOpen(false)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
77
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
if (!open) {
|
|
80
|
+
openList()
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
if (filtered.length > 0) {
|
|
84
|
+
setHighlighted(
|
|
85
|
+
event.key === 'ArrowDown'
|
|
86
|
+
? (activeIndex + 1) % filtered.length
|
|
87
|
+
: (activeIndex <= 0 ? filtered.length : activeIndex) - 1,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if (event.key === 'Enter') {
|
|
93
|
+
if (open && activeIndex >= 0) {
|
|
94
|
+
event.preventDefault()
|
|
95
|
+
pick(filtered[activeIndex]!)
|
|
96
|
+
} else {
|
|
97
|
+
setOpen(false)
|
|
98
|
+
}
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
102
|
+
setOpen(false)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const showEmpty = !loading && query !== '' && filtered.length === 0
|
|
107
|
+
const showList = open && (loading || filtered.length > 0 || showEmpty)
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className={cn('relative', className)}>
|
|
111
|
+
<Input
|
|
112
|
+
value={fieldValue}
|
|
113
|
+
onChange={(event) => {
|
|
114
|
+
onValueChange(event.target.value)
|
|
115
|
+
setQuery(event.target.value)
|
|
116
|
+
setHighlighted(-1)
|
|
117
|
+
setOpen(true)
|
|
118
|
+
}}
|
|
119
|
+
onFocus={openList}
|
|
120
|
+
// oxlint-disable-next-line reform-rules/no-set-timeout-interval -- DOM blur defer; Effect.sleep can't gate a React event handler
|
|
121
|
+
onBlur={() => globalThis.setTimeout(() => setOpen(false), BLUR_CLOSE_DELAY_MS)}
|
|
122
|
+
onKeyDown={onKeyDown}
|
|
123
|
+
placeholder={placeholder}
|
|
124
|
+
disabled={disabled}
|
|
125
|
+
role="combobox"
|
|
126
|
+
aria-label={ariaLabel}
|
|
127
|
+
aria-expanded={showList}
|
|
128
|
+
aria-controls={listId}
|
|
129
|
+
aria-activedescendant={activeIndex >= 0 ? optionId(activeIndex) : undefined}
|
|
130
|
+
aria-autocomplete="list"
|
|
131
|
+
autoComplete="off"
|
|
132
|
+
spellCheck={false}
|
|
133
|
+
className="pr-8"
|
|
134
|
+
/>
|
|
135
|
+
<span className="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground">
|
|
136
|
+
{loading ? (
|
|
137
|
+
<Loader2Icon className="size-4 animate-spin" />
|
|
138
|
+
) : (
|
|
139
|
+
<ChevronDownIcon className="size-4" />
|
|
140
|
+
)}
|
|
141
|
+
</span>
|
|
142
|
+
{showList && (
|
|
143
|
+
<div
|
|
144
|
+
id={listId}
|
|
145
|
+
role="listbox"
|
|
146
|
+
aria-label={`${ariaLabel} suggestions`}
|
|
147
|
+
className="absolute left-0 right-0 top-full z-50 mt-1 flex max-h-60 flex-col overflow-y-auto rounded-md border bg-card p-1 text-card-foreground shadow-md"
|
|
148
|
+
>
|
|
149
|
+
{loading && (
|
|
150
|
+
<span className="flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground">
|
|
151
|
+
<Loader2Icon className="size-3 animate-spin" />
|
|
152
|
+
{loadingText}
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
|
+
{filtered.map((suggestion, index) => (
|
|
156
|
+
<button
|
|
157
|
+
key={suggestion.value}
|
|
158
|
+
id={optionId(index)}
|
|
159
|
+
type="button"
|
|
160
|
+
role="option"
|
|
161
|
+
aria-selected={suggestion.value === fieldValue}
|
|
162
|
+
// mousedown fires before blur so the pick lands
|
|
163
|
+
onMouseDown={(event) => {
|
|
164
|
+
event.preventDefault()
|
|
165
|
+
pick(suggestion)
|
|
166
|
+
}}
|
|
167
|
+
onMouseMove={() => setHighlighted(index)}
|
|
168
|
+
className={cn(
|
|
169
|
+
'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm',
|
|
170
|
+
index === activeIndex ? 'bg-accent text-accent-foreground' : 'hover:bg-accent/50',
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
<span className="flex min-w-0 flex-1 flex-col">
|
|
174
|
+
<span className="truncate">{suggestion.label ?? suggestion.value}</span>
|
|
175
|
+
{suggestion.description ? (
|
|
176
|
+
<span className="truncate text-xs text-muted-foreground">{suggestion.description}</span>
|
|
177
|
+
) : null}
|
|
178
|
+
</span>
|
|
179
|
+
{suggestion.value === fieldValue ? <CheckIcon className="size-4 shrink-0" /> : null}
|
|
180
|
+
</button>
|
|
181
|
+
))}
|
|
182
|
+
{showEmpty && (
|
|
183
|
+
<span className="px-2 py-1.5 text-xs text-muted-foreground">{emptyText}</span>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
}
|
package/src/components/badge.tsx
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/badge.tsx). Full credit to the Better Auth team.
|
|
3
1
|
import * as React from "react";
|
|
4
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
3
|
import type { ClassProp } from "class-variance-authority/types";
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/button.tsx). Full credit to the Better Auth team.
|
|
3
1
|
import { Slot } from "@radix-ui/react-slot";
|
|
4
2
|
import type { VariantProps } from "class-variance-authority";
|
|
5
3
|
import { cva } from "class-variance-authority";
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/combobox.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import * as React from 'react'
|
|
5
2
|
import { Combobox as ComboboxPrimitive } from '@base-ui/react'
|
|
6
3
|
import { CheckIcon, ChevronDownIcon, XIcon } from 'lucide-react'
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/command.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import { Command as CommandPrimitive } from "cmdk";
|
|
5
2
|
import { SearchIcon } from "lucide-react";
|
|
6
3
|
import type * as React from "react";
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/dialog.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
2
|
import { XIcon } from "lucide-react";
|
|
6
3
|
import type * as React from "react";
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/dropdown-menu.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
5
2
|
import { CheckIcon } from "lucide-react";
|
|
6
3
|
import type * as React from "react";
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/github-background.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import type * as React from "react";
|
|
5
2
|
import { useMemo } from "react";
|
|
6
3
|
|
|
@@ -104,7 +101,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
104
101
|
className="absolute inset-0 pointer-events-none overflow-hidden"
|
|
105
102
|
style={{ zIndex: 2 }}
|
|
106
103
|
>
|
|
107
|
-
{/* Radial mask — fades edges into the shader */}
|
|
108
104
|
<div
|
|
109
105
|
className="absolute inset-0"
|
|
110
106
|
style={{
|
|
@@ -113,7 +109,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
113
109
|
"radial-gradient(ellipse 85% 75% at 45% 35%, black 15%, transparent 60%)",
|
|
114
110
|
}}
|
|
115
111
|
>
|
|
116
|
-
{/* ── Contribution Graph ── */}
|
|
117
112
|
<div
|
|
118
113
|
className="absolute"
|
|
119
114
|
style={{
|
|
@@ -153,14 +148,12 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
153
148
|
</svg>
|
|
154
149
|
</div>
|
|
155
150
|
|
|
156
|
-
{/* ── Git Network Graph ── */}
|
|
157
151
|
<svg
|
|
158
152
|
className="absolute inset-0 w-full h-full"
|
|
159
153
|
viewBox="0 0 400 900"
|
|
160
154
|
preserveAspectRatio="xMidYMid slice"
|
|
161
155
|
style={{ opacity: 0.45 }}
|
|
162
156
|
>
|
|
163
|
-
{/* Main branch */}
|
|
164
157
|
<line
|
|
165
158
|
x1="200"
|
|
166
159
|
y1="0"
|
|
@@ -173,7 +166,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
173
166
|
style={{ animationDelay: "0.3s" }}
|
|
174
167
|
/>
|
|
175
168
|
|
|
176
|
-
{/* Feature branch 1 — forks right */}
|
|
177
169
|
<path
|
|
178
170
|
d="M 200 150 C 200 180, 280 200, 280 230 L 280 350 C 280 380, 200 395, 200 400"
|
|
179
171
|
stroke="rgba(255,255,255,0.12)"
|
|
@@ -184,7 +176,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
184
176
|
style={{ animationDelay: "0.8s" }}
|
|
185
177
|
/>
|
|
186
178
|
|
|
187
|
-
{/* Feature branch 2 — forks left */}
|
|
188
179
|
<path
|
|
189
180
|
d="M 200 300 C 200 325, 130 340, 130 360 L 130 500 C 130 525, 200 540, 200 550"
|
|
190
181
|
stroke="rgba(255,255,255,0.10)"
|
|
@@ -195,7 +186,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
195
186
|
style={{ animationDelay: "1.3s" }}
|
|
196
187
|
/>
|
|
197
188
|
|
|
198
|
-
{/* Hotfix branch — short fork right */}
|
|
199
189
|
<path
|
|
200
190
|
d="M 200 550 C 200 570, 260 580, 260 600 L 260 650 C 260 670, 200 680, 200 690"
|
|
201
191
|
stroke="rgba(255,255,255,0.08)"
|
|
@@ -206,7 +196,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
206
196
|
style={{ animationDelay: "1.8s" }}
|
|
207
197
|
/>
|
|
208
198
|
|
|
209
|
-
{/* Feature branch 3 — wide fork left */}
|
|
210
199
|
<path
|
|
211
200
|
d="M 200 620 C 200 640, 110 660, 110 680 L 110 800 C 110 820, 200 835, 200 850"
|
|
212
201
|
stroke="rgba(255,255,255,0.07)"
|
|
@@ -217,7 +206,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
217
206
|
style={{ animationDelay: "2.2s" }}
|
|
218
207
|
/>
|
|
219
208
|
|
|
220
|
-
{/* Commit nodes */}
|
|
221
209
|
{COMMIT_GROUPS.map((group) =>
|
|
222
210
|
group.ys.map((nodeY, index) => (
|
|
223
211
|
<circle
|
|
@@ -242,7 +230,6 @@ export function GitHubBackground(): React.JSX.Element {
|
|
|
242
230
|
</svg>
|
|
243
231
|
</div>
|
|
244
232
|
|
|
245
|
-
{/* CSS for stroke-dash draw animation */}
|
|
246
233
|
<style>{`
|
|
247
234
|
.git-line-anim {
|
|
248
235
|
stroke-dasharray: 1;
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/halftone-background.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import type * as React from "react";
|
|
5
2
|
import { useEffect, useRef } from "react";
|
|
6
|
-
|
|
7
|
-
const VERTEX_SHADER = `
|
|
8
|
-
attribute vec2 a_position;
|
|
9
|
-
void main() {
|
|
10
|
-
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
11
|
-
}`;
|
|
3
|
+
import { CLICK_DECAY, FULLSCREEN_VERTEX_COUNT, MOUSE_LERP, MOUSE_LERP_IDLE, MS_PER_SECOND, RENDER_SCALE, type ShaderSpec, VERTEX_SHADER } from "./halftone-config";
|
|
12
4
|
|
|
13
5
|
const FRAGMENT_SHADER = `
|
|
14
6
|
precision highp float;
|
|
@@ -77,7 +69,6 @@ void main() {
|
|
|
77
69
|
|
|
78
70
|
vec2 ap = vec2(uv.x * aspect, uv.y);
|
|
79
71
|
|
|
80
|
-
// ═══ SHARED MOUSE WARP ═══
|
|
81
72
|
vec2 scaledP = ap * 2.5;
|
|
82
73
|
vec2 mUV = u_mouse;
|
|
83
74
|
mUV.x *= aspect;
|
|
@@ -97,7 +88,6 @@ void main() {
|
|
|
97
88
|
warp += normalize(cD + 0.001) * sin(cDist * 12.0 - time * 8.0) * u_click * 0.4 * exp(-cDist * 2.0);
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
// ═══ NOISE LAYER ═══
|
|
101
91
|
vec2 np = scaledP + warp;
|
|
102
92
|
vec2 q = vec2(fbm(np + t * 0.3), fbm(np + vec2(5.2, 1.3) + t * 0.2));
|
|
103
93
|
vec2 r = vec2(
|
|
@@ -107,7 +97,6 @@ void main() {
|
|
|
107
97
|
float f = fbm(np + 4.0 * r);
|
|
108
98
|
float noise = clamp(f*f*f + 0.6*f*f + 0.5*f, 0.0, 1.0) * 0.15;
|
|
109
99
|
|
|
110
|
-
// ═══ CONTRIBUTION GRID ═══
|
|
111
100
|
vec2 gp = ap;
|
|
112
101
|
gp += vec2(snoise(ap * 3.0 + t), snoise(ap * 3.0 + t + 50.0)) * 0.004;
|
|
113
102
|
gp += warp * 0.04;
|
|
@@ -135,7 +124,6 @@ void main() {
|
|
|
135
124
|
float gridReveal = smoothstep(0.5, 3.0, time);
|
|
136
125
|
float grid = inSquare * level * gFade * gridReveal * 0.10;
|
|
137
126
|
|
|
138
|
-
// ═══ COMPOSE ═══
|
|
139
127
|
vec2 vc = uv - 0.5;
|
|
140
128
|
float vignette = 1.0 - dot(vc, vc) * 0.3;
|
|
141
129
|
|
|
@@ -143,15 +131,6 @@ void main() {
|
|
|
143
131
|
gl_FragColor = vec4(vec3(col), 1.0);
|
|
144
132
|
}`;
|
|
145
133
|
|
|
146
|
-
const RENDER_SCALE = 0.5;
|
|
147
|
-
const MS_PER_SECOND = 1000;
|
|
148
|
-
const MOUSE_LERP = 0.08;
|
|
149
|
-
const MOUSE_LERP_IDLE = 0.02;
|
|
150
|
-
const CLICK_DECAY = 0.96;
|
|
151
|
-
const FULLSCREEN_VERTEX_COUNT = 6;
|
|
152
|
-
|
|
153
|
-
type ShaderSpec = { readonly type: number; readonly source: string };
|
|
154
|
-
|
|
155
134
|
export function HalftoneBackground(): React.JSX.Element {
|
|
156
135
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
157
136
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const VERTEX_SHADER = `
|
|
2
|
+
attribute vec2 a_position;
|
|
3
|
+
void main() {
|
|
4
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
5
|
+
}`;
|
|
6
|
+
|
|
7
|
+
export const RENDER_SCALE = 0.5;
|
|
8
|
+
export const MS_PER_SECOND = 1000;
|
|
9
|
+
export const MOUSE_LERP = 0.08;
|
|
10
|
+
export const MOUSE_LERP_IDLE = 0.02;
|
|
11
|
+
export const CLICK_DECAY = 0.96;
|
|
12
|
+
export const FULLSCREEN_VERTEX_COUNT = 6;
|
|
13
|
+
|
|
14
|
+
export type ShaderSpec = { readonly type: number; readonly source: string };
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/input-group.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import type * as React from "react";
|
|
5
2
|
import type { ComponentProps } from "react";
|
|
6
3
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
@@ -19,17 +16,11 @@ function InputGroup({ className, ...props }: ComponentProps<"div">): React.JSX.E
|
|
|
19
16
|
className={cn(
|
|
20
17
|
"group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
|
|
21
18
|
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
22
|
-
|
|
23
|
-
// Variants based on alignment.
|
|
24
19
|
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
25
20
|
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
|
26
21
|
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
27
22
|
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
28
|
-
|
|
29
|
-
// Focus state.
|
|
30
23
|
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
31
|
-
|
|
32
|
-
// Error state.
|
|
33
24
|
"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
34
25
|
|
|
35
26
|
className,
|
package/src/components/input.tsx
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/live-duration.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import { DateTime, Option } from "effect";
|
|
5
2
|
import type * as React from "react";
|
|
6
3
|
import { useState, useEffect } from "react";
|
|
@@ -15,7 +12,6 @@ interface FormatDurationInputExternalApi {
|
|
|
15
12
|
completedAt: string | null;
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
// Inlined from "@/lib/utils" (formatDuration) — only this helper is used here.
|
|
19
15
|
function formatDuration({ startedAt, completedAt }: FormatDurationInputExternalApi): string {
|
|
20
16
|
if (!startedAt) {
|
|
21
17
|
return "";
|
|
@@ -36,9 +32,7 @@ function formatDuration({ startedAt, completedAt }: FormatDurationInputExternalA
|
|
|
36
32
|
return `${seconds}s`;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
//
|
|
40
|
-
// Shared tick for LiveDuration components. One interval drives all subscribers
|
|
41
|
-
// instead of each component running its own 1s interval.
|
|
35
|
+
// shared tick: one interval drives all LiveDuration subscribers
|
|
42
36
|
const callbacks = new Set<() => void>();
|
|
43
37
|
const tickState: { intervalId: Option.Option<ReturnType<typeof setInterval>> } = {
|
|
44
38
|
intervalId: Option.none(),
|
|
@@ -68,7 +62,6 @@ interface LiveDurationPropsExternalApi {
|
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
export function LiveDuration({ startedAt, completedAt, className }: LiveDurationPropsExternalApi): React.ReactNode {
|
|
71
|
-
// tick forces re-renders when subscribeLiveTick fires; value is unused in render
|
|
72
65
|
const [, setTick] = useState(0);
|
|
73
66
|
|
|
74
67
|
useEffect(() => {
|
package/src/components/logo.tsx
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/popover.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import type * as React from "react";
|
|
5
2
|
import type { ComponentProps } from "react";
|
|
6
3
|
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/resize-handle.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import type * as React from "react";
|
|
5
2
|
import { useCallback, useEffect, useState } from "react";
|
|
6
3
|
import type { MouseEvent as ReactMouseEvent } from "react";
|
|
7
4
|
import { cn } from "../foundation/cn";
|
|
8
5
|
|
|
9
6
|
interface ResizeHandlePropsExternalApi {
|
|
10
|
-
/** Called continuously during drag with the pointer position on the resize axis
|
|
11
|
-
* (clientX for `axis: "x"`, clientY for `axis: "y"`). */
|
|
12
7
|
onResize: (clientPos: number) => void;
|
|
13
|
-
/** Called when drag starts */
|
|
14
8
|
onDragStart?: () => void;
|
|
15
|
-
/** Called when drag ends */
|
|
16
9
|
onDragEnd?: () => void;
|
|
17
|
-
/** Double click handler (e.g. reset to default) */
|
|
18
10
|
onDoubleClick?: () => void;
|
|
19
|
-
/** Resize axis: "x" (vertical handle, horizontal drag — default) or "y" (horizontal
|
|
20
|
-
* handle, vertical drag — for a top/bottom panel edge). */
|
|
21
11
|
axis?: "x" | "y";
|
|
22
12
|
className?: string;
|
|
23
13
|
}
|
|
@@ -79,14 +69,12 @@ export function ResizeHandle({
|
|
|
79
69
|
className,
|
|
80
70
|
)}
|
|
81
71
|
>
|
|
82
|
-
{/* Wider invisible hit area */}
|
|
83
72
|
<div
|
|
84
73
|
className={cn(
|
|
85
74
|
"absolute",
|
|
86
75
|
vertical ? "inset-x-0 -top-[5px] h-[11px]" : "inset-y-0 -left-[5px] w-[11px]",
|
|
87
76
|
)}
|
|
88
77
|
/>
|
|
89
|
-
{/* Visible line */}
|
|
90
78
|
<div
|
|
91
79
|
className={cn(
|
|
92
80
|
"absolute transition-all duration-150",
|
package/src/components/sheet.tsx
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/sheet.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
2
|
import { XIcon } from "lucide-react";
|
|
6
3
|
import type * as React from "react";
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/time-ago.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import { DateTime } from "effect";
|
|
5
2
|
import type * as React from "react";
|
|
6
3
|
import { useState, useEffect } from 'react';
|
|
@@ -14,7 +11,6 @@ const SECONDS_PER_MONTH = 2592000;
|
|
|
14
11
|
const MONTHS_PER_YEAR = 12;
|
|
15
12
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
16
13
|
|
|
17
|
-
// Inlined from better-hub's "@/lib/utils" (timeAgo) — only the piece used here.
|
|
18
14
|
function timeAgo(date: string | Date): string {
|
|
19
15
|
const now = DateTime.unsafeNow();
|
|
20
16
|
const then = DateTime.unsafeMake(date);
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
|
|
2
|
-
// (apps/web/src/components/ui/tooltip.tsx). Full credit to the Better Auth team.
|
|
3
|
-
|
|
4
1
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
5
2
|
import type * as React from "react";
|
|
6
3
|
import { cn } from "../foundation/cn";
|
package/src/foundation/cn.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { type ClassValue, clsx } from 'clsx'
|
|
2
2
|
import { twMerge } from 'tailwind-merge'
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Merge class names with Tailwind-aware de-duplication.
|
|
6
|
-
* Adapted from better-hub (apps/web/src/lib/utils.ts). See ../../README.md.
|
|
7
|
-
*/
|
|
8
4
|
// oxlint-disable-next-line reform-rules/min-var-name -- `cn` is the canonical shadcn/Tailwind class-merge helper, a public export consumed across the monorepo
|
|
9
5
|
export const cn = (...inputs: ClassValue[]): string => twMerge(clsx(inputs))
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
// This package's primitives are adapted from better-hub by Better Auth
|
|
2
|
-
// (https://github.com/better-auth/better-hub) — full credit to the original
|
|
3
|
-
// authors. See README.md for details.
|
|
4
|
-
|
|
5
1
|
export * from './components/agent-icon';
|
|
2
|
+
export * from './components/autocomplete';
|
|
6
3
|
export * from './components/badge';
|
|
7
4
|
export * from './components/button';
|
|
8
5
|
export * from './components/combobox';
|