@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@playfast/reform-better-hub-ui-primitives",
3
3
  "playbook": "./playbook",
4
- "version": "0.2.1",
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": [
@@ -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/agent-icon.tsx). Full credit to the Better Auth team.
3
-
4
1
  import type * as React from "react";
5
2
 
6
3
  interface AgentIconPropsExternalApi {
@@ -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
+ }
@@ -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,
@@ -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.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
 
@@ -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
- // Inlined from "@/lib/live-tick".
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(() => {
@@ -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/logo.tsx). Full credit to the Better Auth team.
3
1
  import type * as React from "react";
4
2
  import { cn } from '../foundation/cn';
5
3
 
@@ -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",
@@ -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,5 +1,3 @@
1
- // Adapted from better-hub by Better Auth — https://github.com/better-auth/better-hub
2
- // (apps/web/src/components/ui/textarea.tsx). Full credit to the Better Auth team.
3
1
  import type * as React from "react";
4
2
  import type { ComponentProps } from "react";
5
3
 
@@ -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";
@@ -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';