@workerdeck/ui 0.23.0 → 1.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 (145) hide show
  1. package/build/{SessionPanel-DUt2VzXG.mjs → SessionPanel-DDgBkHsi.mjs} +2151 -3357
  2. package/build/SessionPanel-DDgBkHsi.mjs.map +1 -0
  3. package/build/SessionPanel-E_L_ldQ5.d.mts +207 -0
  4. package/build/format-B8ZM9LPy.d.mts +16 -0
  5. package/build/format.d.mts +8 -75
  6. package/build/format.mjs +2 -2
  7. package/build/index.d.mts +63 -1138
  8. package/build/index.mjs +4 -300
  9. package/build/index.mjs.map +1 -1
  10. package/build/scoped.css +15 -23
  11. package/build/{status-BE-zg88x.mjs → status-C0HSLZas.mjs} +14 -85
  12. package/build/status-C0HSLZas.mjs.map +1 -0
  13. package/build/workspace.d.mts +5 -126
  14. package/build/workspace.mjs +11 -143
  15. package/build/workspace.mjs.map +1 -1
  16. package/package.json +4 -4
  17. package/src/components/agent/CodeEditor.tsx +31 -103
  18. package/src/components/agent/Composer.tsx +114 -333
  19. package/src/components/agent/ContextDialog.tsx +20 -41
  20. package/src/components/agent/ContextRing.tsx +6 -28
  21. package/src/components/agent/Conversation.tsx +21 -33
  22. package/src/components/agent/EditorTabs.tsx +39 -67
  23. package/src/components/agent/EngineIcon.tsx +29 -72
  24. package/src/components/agent/EntryIcon.tsx +16 -0
  25. package/src/components/agent/FileCard.tsx +10 -16
  26. package/src/components/agent/FileTree.tsx +52 -125
  27. package/src/components/agent/FileViewer.tsx +30 -60
  28. package/src/components/agent/HostFilesDialog.tsx +40 -69
  29. package/src/components/agent/Loader.tsx +3 -24
  30. package/src/components/agent/McpDialog.tsx +64 -137
  31. package/src/components/agent/Message.tsx +2 -21
  32. package/src/components/agent/ModelSelect.tsx +33 -62
  33. package/src/components/agent/PermissionModeSelect.tsx +21 -79
  34. package/src/components/agent/PermissionPrompt.tsx +59 -63
  35. package/src/components/agent/ProjectIcon.tsx +228 -93
  36. package/src/components/agent/PromptTokenText.tsx +10 -16
  37. package/src/components/agent/QuestionPrompt.tsx +43 -64
  38. package/src/components/agent/Reasoning.tsx +13 -16
  39. package/src/components/agent/Response.tsx +3 -9
  40. package/src/components/agent/Scrubber.tsx +31 -80
  41. package/src/components/agent/SessionBrowser.tsx +64 -243
  42. package/src/components/agent/SessionEmptyState.tsx +10 -40
  43. package/src/components/agent/SessionInfoDialog.tsx +46 -78
  44. package/src/components/agent/SessionItem.tsx +53 -320
  45. package/src/components/agent/SessionList.tsx +23 -39
  46. package/src/components/agent/SessionPanel.tsx +315 -996
  47. package/src/components/agent/SessionStatusIcon.tsx +14 -28
  48. package/src/components/agent/SessionSteps.tsx +34 -187
  49. package/src/components/agent/SessionWorkspace.tsx +104 -219
  50. package/src/components/agent/SkillsDialog.tsx +35 -80
  51. package/src/components/agent/StatusBar.tsx +51 -136
  52. package/src/components/agent/SubagentStrip.tsx +12 -77
  53. package/src/components/agent/ToolCallCard.tsx +94 -135
  54. package/src/components/agent/Transcript.tsx +103 -1288
  55. package/src/components/agent/TranscriptItemView.tsx +155 -0
  56. package/src/components/agent/TranscriptRows.tsx +418 -0
  57. package/src/components/agent/UsageDialog.tsx +7 -29
  58. package/src/components/agent/UsageMeters.tsx +18 -73
  59. package/src/components/agent/pulse.tsx +9 -32
  60. package/src/components/agent/scrubber-marks.ts +33 -103
  61. package/src/components/agent/status.ts +1 -5
  62. package/src/components/agent/tool-result-fetch.tsx +3 -25
  63. package/src/components/agent/tool-result-image.tsx +32 -111
  64. package/src/components/agent/transcript-rows.ts +41 -118
  65. package/src/components/agent/transcript-variant.tsx +3 -80
  66. package/src/components/agent/use-height-epoch.ts +10 -24
  67. package/src/components/agent/use-path-links.ts +45 -63
  68. package/src/components/agent/use-subagent-frame.ts +106 -0
  69. package/src/components/agent/use-transcript-jumps.ts +49 -107
  70. package/src/components/prompt-area/animated-placeholder.tsx +6 -5
  71. package/src/components/prompt-area/clipboard-helpers.ts +26 -21
  72. package/src/components/prompt-area/cursor-helpers.ts +79 -37
  73. package/src/components/prompt-area/dom-helpers.ts +97 -69
  74. package/src/components/prompt-area/file-strip.tsx +90 -80
  75. package/src/components/prompt-area/html-to-markdown.ts +67 -46
  76. package/src/components/prompt-area/image-strip.tsx +8 -18
  77. package/src/components/prompt-area/index.ts +2 -15
  78. package/src/components/prompt-area/prompt-area-engine.ts +100 -115
  79. package/src/components/prompt-area/prompt-area-list-ops.ts +66 -59
  80. package/src/components/prompt-area/prompt-area.tsx +31 -38
  81. package/src/components/prompt-area/remove-button.tsx +3 -9
  82. package/src/components/prompt-area/segment-helpers.ts +4 -12
  83. package/src/components/prompt-area/trigger-popover.tsx +15 -27
  84. package/src/components/prompt-area/trigger-presets.ts +0 -10
  85. package/src/components/prompt-area/types.ts +3 -8
  86. package/src/components/prompt-area/use-chip-editing.ts +305 -0
  87. package/src/components/prompt-area/use-markdown-mode.ts +7 -17
  88. package/src/components/prompt-area/use-prompt-area-events.ts +80 -86
  89. package/src/components/prompt-area/use-prompt-area-keydown.ts +598 -0
  90. package/src/components/prompt-area/use-prompt-area-state.ts +5 -23
  91. package/src/components/prompt-area/use-prompt-area.ts +170 -896
  92. package/src/components/prompt-area/use-trigger-search.ts +21 -19
  93. package/src/components/terminal/PermissionPrompt.tsx +56 -59
  94. package/src/components/terminal/QuestionPrompt.tsx +28 -79
  95. package/src/components/terminal/StatusLine.tsx +13 -63
  96. package/src/components/terminal/TerminalTranscript.tsx +44 -160
  97. package/src/components/terminal/affordances.tsx +68 -98
  98. package/src/components/terminal/blocks.ts +20 -156
  99. package/src/components/terminal/diff.tsx +19 -67
  100. package/src/components/terminal/height.ts +163 -313
  101. package/src/components/terminal/image-box.ts +0 -44
  102. package/src/components/terminal/items.tsx +150 -305
  103. package/src/components/terminal/markdown.tsx +36 -95
  104. package/src/components/terminal/press.tsx +26 -53
  105. package/src/components/terminal/prompt.tsx +40 -170
  106. package/src/components/terminal/result-preview.ts +8 -56
  107. package/src/components/terminal/row.tsx +11 -98
  108. package/src/components/terminal/scrubber.tsx +108 -401
  109. package/src/components/terminal/surface.tsx +6 -54
  110. package/src/components/terminal/todos.ts +66 -0
  111. package/src/components/terminal/tool-run.ts +21 -192
  112. package/src/components/ui/AlertDialog.tsx +8 -23
  113. package/src/components/ui/Badge.tsx +18 -29
  114. package/src/components/ui/Button.tsx +7 -15
  115. package/src/components/ui/Card.tsx +2 -13
  116. package/src/components/ui/CodeBlock.tsx +10 -37
  117. package/src/components/ui/CopyButton.tsx +10 -23
  118. package/src/components/ui/Dialog.tsx +23 -36
  119. package/src/components/ui/Empty.tsx +6 -29
  120. package/src/components/ui/Input.tsx +14 -16
  121. package/src/components/ui/Menu.tsx +6 -18
  122. package/src/components/ui/PortalScope.tsx +1 -21
  123. package/src/components/ui/ProgressRing.tsx +8 -19
  124. package/src/components/ui/Select.tsx +18 -31
  125. package/src/components/ui/Sonner.tsx +1 -2
  126. package/src/components/ui/Spinner.tsx +1 -1
  127. package/src/components/ui/Splitter.tsx +30 -64
  128. package/src/components/ui/Textarea.tsx +2 -5
  129. package/src/components/ui/Tooltip.tsx +3 -13
  130. package/src/format.ts +0 -9
  131. package/src/index.ts +16 -113
  132. package/src/lib/clipboard.ts +8 -28
  133. package/src/lib/css.ts +8 -0
  134. package/src/lib/format.ts +101 -86
  135. package/src/lib/plan-request.ts +16 -0
  136. package/src/lib/status.ts +45 -95
  137. package/src/lib/tool-icon.ts +39 -47
  138. package/src/lib/utils.ts +1 -3
  139. package/src/styles/terminal.css +81 -49
  140. package/src/styles/theme.css +96 -32
  141. package/src/workspace.ts +1 -24
  142. package/build/SessionPanel-BobynUo4.d.mts +0 -669
  143. package/build/SessionPanel-DUt2VzXG.mjs.map +0 -1
  144. package/build/format-ljc3lKpA.d.mts +0 -59
  145. package/build/status-BE-zg88x.mjs.map +0 -1
@@ -4,66 +4,39 @@ import { cn } from '../../lib/utils.ts'
4
4
 
5
5
  export interface CodeBlockProps {
6
6
  code: string
7
- /** Header label, e.g. a language or "Parameters". */
8
7
  label?: ReactNode
9
8
  copyable?: boolean
10
- /**
11
- * `'panel'` (default) is the framed card: border, header strip, rounded.
12
- * `'plain'` is the terminal treatment — the label is a dim line, the code a
13
- * flat band, and copy is a character. For surfaces where a box around every
14
- * payload is more chrome than content (the transcript's `lines` variant).
15
- *
16
- * A prop rather than a read of the transcript's variant context: this is a
17
- * `ui/` primitive and has no business knowing what a transcript is. The agent
18
- * component that renders it does.
19
- */
20
9
  variant?: 'panel' | 'plain'
21
10
  className?: string
22
11
  }
23
12
 
24
- /** Plain (unhighlighted) code panel for structured data like tool inputs. Markdown code
25
- * inside assistant responses is highlighted by <Response> instead. */
26
- export function CodeBlock({
27
- code,
28
- label,
29
- copyable = true,
30
- variant = 'panel',
31
- className,
32
- }: CodeBlockProps) {
13
+ export function CodeBlock({ code, label, copyable = true, variant = 'panel', className }: CodeBlockProps) {
33
14
  const plain = variant === 'plain'
34
15
  const header = label !== undefined || copyable
35
16
 
36
17
  if (plain) {
37
18
  return (
38
- <div data-slot='code-block' data-variant='plain' className={cn('min-w-0', className)}>
19
+ <div data-slot="code-block" data-variant="plain" className={cn('min-w-0', className)}>
39
20
  {header ? (
40
- <div className='flex items-baseline justify-between gap-2'>
41
- <span className='truncate text-label leading-5 text-fg-4'>{label}</span>
42
- {copyable ? (
43
- <CopyButton glyph value={code} className='size-5 shrink-0 rounded-sm' />
44
- ) : null}
21
+ <div className="flex items-baseline justify-between gap-2">
22
+ <span className="truncate text-label leading-5 text-fg-4">{label}</span>
23
+ {copyable ? <CopyButton glyph value={code} className="size-5 shrink-0 rounded-sm" /> : null}
45
24
  </div>
46
25
  ) : null}
47
- <pre className='max-h-64 overflow-auto bg-code-bg px-2 font-mono text-label leading-5 whitespace-pre-wrap text-fg-2'>
48
- {code}
49
- </pre>
26
+ <pre className="max-h-64 overflow-auto bg-code-bg px-2 font-mono text-label leading-5 whitespace-pre-wrap text-fg-2">{code}</pre>
50
27
  </div>
51
28
  )
52
29
  }
53
30
 
54
31
  return (
55
- <div
56
- data-slot='code-block'
57
- className={cn('overflow-hidden rounded-md border border-border bg-code-bg', className)}>
32
+ <div data-slot="code-block" className={cn('overflow-hidden rounded-md border border-border bg-code-bg', className)}>
58
33
  {header ? (
59
- <div className='flex h-8 items-center justify-between border-b border-border px-2.5'>
60
- <span className='font-mono text-label text-fg-3'>{label}</span>
34
+ <div className="flex h-8 items-center justify-between border-b border-border px-2.5">
35
+ <span className="font-mono text-label text-fg-3">{label}</span>
61
36
  {copyable ? <CopyButton value={code} /> : null}
62
37
  </div>
63
38
  ) : null}
64
- <pre className='max-h-64 overflow-auto px-3 py-2 font-mono text-label whitespace-pre-wrap text-fg-2'>
65
- {code}
66
- </pre>
39
+ <pre className="max-h-64 overflow-auto px-3 py-2 font-mono text-label whitespace-pre-wrap text-fg-2">{code}</pre>
67
40
  </div>
68
41
  )
69
42
  }
@@ -6,47 +6,34 @@ import { cn } from '../../lib/utils.ts'
6
6
 
7
7
  export interface CopyButtonProps extends Omit<ButtonProps, 'onClick' | 'children'> {
8
8
  value: string
9
- /** Draw the state as characters (`⧉` / `✓`) rather than line-art icons — for
10
- * terminal-styled surfaces, where an SVG reads as another app's button. */
11
9
  glyph?: boolean
12
10
  }
13
11
 
14
- export function CopyButton({
15
- value,
16
- glyph,
17
- className,
18
- variant = 'ghost',
19
- size = 'icon-sm',
20
- ...props
21
- }: CopyButtonProps) {
12
+ export function CopyButton({ value, glyph, className, variant = 'ghost', size = 'icon-sm', ...props }: CopyButtonProps) {
22
13
  const [copied, setCopied] = useState(false)
23
14
  return (
24
15
  <Button
25
16
  variant={variant}
26
17
  size={size}
27
- aria-label='Copy'
18
+ aria-label="Copy"
28
19
  className={cn('text-fg-3', className)}
29
20
  onClick={() => {
30
- // Through `copyText`, which falls back for insecure origins — the
31
- // dashboard on a LAN address has no `navigator.clipboard` at all, and
32
- // reaching straight for `.writeText` there throws.
33
21
  void copyText(value).then((ok) => {
34
- // Only tick when it really copied: a check mark over an empty
35
- // clipboard is worse than no feedback.
36
- if (!ok) return
22
+ if (!ok) {
23
+ return
24
+ }
37
25
  setCopied(true)
38
26
  setTimeout(() => setCopied(false), 1500)
39
27
  })
40
28
  }}
41
- {...props}>
29
+ {...props}
30
+ >
42
31
  {glyph ? (
43
- <span className={cn('font-mono text-body-sm leading-5', copied && 'text-success')}>
44
- {copied ? '✓' : '⧉'}
45
- </span>
32
+ <span className={cn('font-mono text-body-sm leading-5', copied && 'text-success')}>{copied ? '✓' : '⧉'}</span>
46
33
  ) : copied ? (
47
- <Check className='size-3.5 text-success' />
34
+ <Check className="size-3.5 text-success" />
48
35
  ) : (
49
- <Copy className='size-3.5' />
36
+ <Copy className="size-3.5" />
50
37
  )}
51
38
  </Button>
52
39
  )
@@ -8,16 +8,12 @@ export const Dialog = DialogPrimitive.Root
8
8
  export const DialogTrigger = DialogPrimitive.Trigger
9
9
  export const DialogClose = DialogPrimitive.Close
10
10
 
11
- /**
12
- * A dismissible panel, sized for reading rather than confirming — the web
13
- * counterpart of the iOS app's detail sheets (context, usage, session info, MCP).
14
- *
15
- * Taller than {@link AlertDialogContent} and scrollable inside, because these
16
- * carry lists whose length is the engine's business, not the layout's.
17
- */
18
- export const DialogContent: FunctionComponent<
19
- DialogPrimitive.Popup.Props & { size?: 'sm' | 'md' | 'lg' }
20
- > = ({ className, children, size = 'md', ...props }) => (
11
+ export const DialogContent: FunctionComponent<DialogPrimitive.Popup.Props & { size?: 'sm' | 'md' | 'lg' }> = ({
12
+ className,
13
+ children,
14
+ size = 'md',
15
+ ...props
16
+ }) => (
21
17
  <DialogPrimitive.Portal>
22
18
  <PortalScope>
23
19
  <DialogPrimitive.Backdrop
@@ -28,7 +24,7 @@ export const DialogContent: FunctionComponent<
28
24
  )}
29
25
  />
30
26
  <DialogPrimitive.Popup
31
- data-slot='dialog-content'
27
+ data-slot="dialog-content"
32
28
  className={cn(
33
29
  'fixed top-1/2 left-1/2 z-70 flex max-h-[min(42rem,calc(100dvh-3rem))] -translate-x-1/2 -translate-y-1/2 flex-col',
34
30
  size === 'sm' && 'w-[min(24rem,calc(100vw-2rem))]',
@@ -40,56 +36,47 @@ export const DialogContent: FunctionComponent<
40
36
  'data-ending-style:scale-95 data-ending-style:opacity-0',
41
37
  className,
42
38
  )}
43
- {...props}>
39
+ {...props}
40
+ >
44
41
  {children}
45
42
  </DialogPrimitive.Popup>
46
43
  </PortalScope>
47
44
  </DialogPrimitive.Portal>
48
45
  )
49
46
 
50
- /** Title row with the close button, pinned above the scrolling body. */
51
47
  export const DialogHeader: FunctionComponent<{
52
48
  title: string
53
49
  description?: string
54
- /** Rendered between the title and the close button. */
55
50
  actions?: React.ReactNode
56
51
  }> = ({ title, description, actions }) => (
57
- <div className='flex items-start gap-2 border-b border-border px-4 py-3'>
58
- <div className='min-w-0 flex-1'>
59
- <DialogPrimitive.Title className='truncate text-body-sm font-semibold text-text'>
60
- {title}
61
- </DialogPrimitive.Title>
52
+ <div className="flex items-start gap-2 border-b border-border px-4 py-3">
53
+ <div className="min-w-0 flex-1">
54
+ <DialogPrimitive.Title className="truncate text-body-sm font-semibold text-text">{title}</DialogPrimitive.Title>
62
55
  {description ? (
63
- <DialogPrimitive.Description className='mt-0.5 text-label text-fg-4'>
64
- {description}
65
- </DialogPrimitive.Description>
56
+ <DialogPrimitive.Description className="mt-0.5 text-label text-fg-4">{description}</DialogPrimitive.Description>
66
57
  ) : null}
67
58
  </div>
68
59
  {actions}
69
60
  <DialogPrimitive.Close
70
- aria-label='Close'
71
- className='-mr-1 flex size-6 shrink-0 items-center justify-center rounded-md text-fg-3 transition-colors outline-none hover:bg-surface-hover hover:text-fg-1'>
72
- <X className='size-3.5' />
61
+ aria-label="Close"
62
+ className="-mr-1 flex size-6 shrink-0 items-center justify-center rounded-md text-fg-3 transition-colors outline-none hover:bg-surface-hover hover:text-fg-1"
63
+ >
64
+ <X className="size-3.5" />
73
65
  </DialogPrimitive.Close>
74
66
  </div>
75
67
  )
76
68
 
77
- /** The scrolling region under the header. */
78
- export const DialogBody: FunctionComponent<React.HTMLAttributes<HTMLDivElement>> = ({
79
- className,
80
- ...props
81
- }) => <div className={cn('min-h-0 flex-1 overflow-y-auto p-4', className)} {...props} />
69
+ export const DialogBody: FunctionComponent<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
70
+ <div className={cn('min-h-0 flex-1 overflow-y-auto p-4', className)} {...props} />
71
+ )
82
72
 
83
- /** A label/value row — the shape every one of these panels is mostly made of. */
84
73
  export const DialogRow: FunctionComponent<{
85
74
  label: string
86
75
  children: React.ReactNode
87
76
  mono?: boolean
88
77
  }> = ({ label, children, mono }) => (
89
- <div className='flex items-baseline justify-between gap-4 py-1.5'>
90
- <span className='shrink-0 text-label text-fg-3'>{label}</span>
91
- <span className={cn('min-w-0 truncate text-right text-body-sm text-fg-1', mono && 'font-mono text-label')}>
92
- {children}
93
- </span>
78
+ <div className="flex items-baseline justify-between gap-4 py-1.5">
79
+ <span className="shrink-0 text-label text-fg-3">{label}</span>
80
+ <span className={cn('min-w-0 truncate text-right text-body-sm text-fg-1', mono && 'font-mono text-label')}>{children}</span>
94
81
  </div>
95
82
  )
@@ -1,22 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { Button } from './Button.tsx'
3
3
 
4
- /**
5
- * A view with nothing in it: icon, title, description, and at most one action.
6
- *
7
- * Centered and given room, because an empty view is the first thing a new
8
- * install shows and a flush-left sentence in the top corner reads like a bug
9
- * report. It lives here rather than in one client because every panel that can
10
- * be empty should be empty the same way — the extension's views and the
11
- * dashboard's four sidebars are the same shape of thing.
12
- *
13
- * **At most one action, and never one the view header already offers.** Creating
14
- * a session and adding a gateway are the `+` in the native title bar, exclusively
15
- * — so those empty states point at it in words rather than growing a second
16
- * button that does the same thing two inches lower. A button here is for the way
17
- * out of a state the header has no answer to: clearing a filter, widening a
18
- * scope.
19
- */
20
4
  export function Empty({
21
5
  icon,
22
6
  title,
@@ -31,16 +15,12 @@ export function Empty({
31
15
  onAction?: () => void
32
16
  }) {
33
17
  return (
34
- <div className='flex flex-col items-center gap-2 px-5 py-8 text-center'>
35
- <div className='text-fg-4 opacity-60 [&_svg]:size-7'>{icon}</div>
36
- <p className='text-body-sm font-medium text-fg-2'>{title}</p>
37
- {description ? (
38
- <p className='max-w-[26ch] text-balance text-label leading-relaxed text-fg-4'>
39
- {description}
40
- </p>
41
- ) : null}
18
+ <div className="flex flex-col items-center gap-2 px-5 py-8 text-center">
19
+ <div className="text-fg-4 opacity-60 [&_svg]:size-7">{icon}</div>
20
+ <p className="text-body-sm font-medium text-fg-2">{title}</p>
21
+ {description ? <p className="max-w-[26ch] text-balance text-label leading-relaxed text-fg-4">{description}</p> : null}
42
22
  {action && onAction ? (
43
- <Button variant='outline' size='sm' className='mt-1' onClick={onAction}>
23
+ <Button variant="outline" size="sm" className="mt-1" onClick={onAction}>
44
24
  {action}
45
25
  </Button>
46
26
  ) : null}
@@ -48,9 +28,6 @@ export function Empty({
48
28
  )
49
29
  }
50
30
 
51
- /** A key or icon named inline in an empty state's description — "use the + above". */
52
31
  export function EmptyKey({ children }: { children: ReactNode }) {
53
- return (
54
- <span className='rounded border border-border px-1 font-mono text-fg-3'>{children}</span>
55
- )
32
+ return <span className="rounded border border-border px-1 font-mono text-fg-3">{children}</span>
56
33
  }
@@ -1,20 +1,18 @@
1
1
  import { type InputHTMLAttributes, forwardRef } from 'react'
2
2
  import { cn } from '../../lib/utils.ts'
3
3
 
4
- export const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>(
5
- ({ className, ...props }, ref) => (
6
- <input
7
- ref={ref}
8
- data-slot='input'
9
- className={cn(
10
- 'h-8 w-full rounded-md border border-border bg-bg px-2.5 text-body-sm text-text',
11
- 'placeholder:text-fg-4 transition-colors outline-none',
12
- 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',
13
- 'disabled:pointer-events-none disabled:opacity-50',
14
- className,
15
- )}
16
- {...props}
17
- />
18
- ),
19
- )
4
+ export const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>(({ className, ...props }, ref) => (
5
+ <input
6
+ ref={ref}
7
+ data-slot="input"
8
+ className={cn(
9
+ 'h-8 w-full rounded-md border border-border bg-bg px-2.5 text-body-sm text-text',
10
+ 'placeholder:text-fg-4 transition-colors outline-none',
11
+ 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',
12
+ 'disabled:pointer-events-none disabled:opacity-50',
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
20
18
  Input.displayName = 'Input'
@@ -7,18 +7,13 @@ export const Menu = MenuPrimitive.Root
7
7
  export const MenuTrigger = MenuPrimitive.Trigger
8
8
 
9
9
  export const MenuContent: FunctionComponent<
10
- MenuPrimitive.Popup.Props &
11
- Pick<MenuPrimitive.Positioner.Props, 'align' | 'side' | 'sideOffset'>
10
+ MenuPrimitive.Popup.Props & Pick<MenuPrimitive.Positioner.Props, 'align' | 'side' | 'sideOffset'>
12
11
  > = ({ className, align = 'end', side = 'bottom', sideOffset = 6, ...props }) => (
13
12
  <MenuPrimitive.Portal>
14
13
  <PortalScope>
15
- <MenuPrimitive.Positioner
16
- align={align}
17
- side={side}
18
- sideOffset={sideOffset}
19
- className='isolate z-80 outline-none'>
14
+ <MenuPrimitive.Positioner align={align} side={side} sideOffset={sideOffset} className="isolate z-80 outline-none">
20
15
  <MenuPrimitive.Popup
21
- data-slot='menu-content'
16
+ data-slot="menu-content"
22
17
  className={cn(
23
18
  'min-w-48 rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',
24
19
  'transition-[opacity,transform] duration-(--motion-base)',
@@ -33,13 +28,9 @@ export const MenuContent: FunctionComponent<
33
28
  </MenuPrimitive.Portal>
34
29
  )
35
30
 
36
- export const MenuItem: FunctionComponent<MenuPrimitive.Item.Props & { destructive?: boolean }> = ({
37
- className,
38
- destructive,
39
- ...props
40
- }) => (
31
+ export const MenuItem: FunctionComponent<MenuPrimitive.Item.Props & { destructive?: boolean }> = ({ className, destructive, ...props }) => (
41
32
  <MenuPrimitive.Item
42
- data-slot='menu-item'
33
+ data-slot="menu-item"
43
34
  className={cn(
44
35
  'flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-body-sm outline-none select-none',
45
36
  'data-highlighted:bg-surface-hover',
@@ -50,9 +41,6 @@ export const MenuItem: FunctionComponent<MenuPrimitive.Item.Props & { destructiv
50
41
  />
51
42
  )
52
43
 
53
- export const MenuSeparator: FunctionComponent<MenuPrimitive.Separator.Props> = ({
54
- className,
55
- ...props
56
- }) => (
44
+ export const MenuSeparator: FunctionComponent<MenuPrimitive.Separator.Props> = ({ className, ...props }) => (
57
45
  <MenuPrimitive.Separator className={cn('my-1 h-px bg-border', className)} {...props} />
58
46
  )
@@ -1,27 +1,7 @@
1
1
  import { type FunctionComponent, type ReactNode } from 'react'
2
2
 
3
- /**
4
- * Re-establishes the `.wd-root` styling scope inside a portal.
5
- *
6
- * The scoped stylesheet (`@workerdeck/ui/scoped.css`) rewrites every rule to
7
- * live under `.wd-root`, so an embedder's page keeps its own design system and
8
- * the panel keeps this one. Base UI popups (Menu, Dialog, AlertDialog, Select,
9
- * Tooltip) portal to `document.body` — OUTSIDE the embedder's wrapper — so
10
- * without this they would render unstyled, or worse, styled by the host.
11
- *
12
- * `display: contents` (inline, so no stylesheet needs to load for it) makes the
13
- * element generate no box: positioning, hit-testing and the popup's own layout
14
- * are untouched. It still carries the class, which is all the scope needs —
15
- * design tokens declared on `.wd-root` inherit through it, and every
16
- * `.wd-root <x>` rule matches below it. Theme keeps working because the dark
17
- * token block matches `[data-theme='dark']` on ANY ancestor, and `document.body`
18
- * sits under the host's themed `<html>` just like the panel does.
19
- *
20
- * Under the classic `theme.css` integration the class matches no rule and the
21
- * element is inert, so the wrapper is unconditional.
22
- */
23
3
  export const PortalScope: FunctionComponent<{ children?: ReactNode }> = ({ children }) => (
24
- <div className='wd-root' style={{ display: 'contents' }}>
4
+ <div className="wd-root" style={{ display: 'contents' }}>
25
5
  {children}
26
6
  </div>
27
7
  )
@@ -1,46 +1,35 @@
1
1
  import { cn } from '../../lib/utils.ts'
2
2
 
3
3
  export interface ProgressRingProps {
4
- /** Filled share, 0–100 (clamped). */
5
4
  value: number
6
- /** Outer diameter in px. */
7
5
  size?: number
8
6
  strokeWidth?: number
9
7
  className?: string
10
8
  }
11
9
 
12
- /** Tiny SVG progress circle; stroke color comes from `currentColor` so callers set it
13
- * via text color classes (e.g. warning/danger past thresholds). */
14
10
  export function ProgressRing({ value, size = 13, strokeWidth = 2, className }: ProgressRingProps) {
15
11
  const clamped = Math.min(100, Math.max(0, value))
16
12
  const radius = (size - strokeWidth) / 2
17
13
  const circumference = 2 * Math.PI * radius
18
14
  return (
19
15
  <svg
20
- data-slot='progress-ring'
16
+ data-slot="progress-ring"
21
17
  width={size}
22
18
  height={size}
23
19
  viewBox={`0 0 ${size} ${size}`}
24
- role='img'
20
+ role="img"
25
21
  aria-label={`${Math.round(clamped)}%`}
26
- className={cn('shrink-0 -rotate-90', className)}>
22
+ className={cn('shrink-0 -rotate-90', className)}
23
+ >
24
+ <circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="currentColor" strokeOpacity={0.2} strokeWidth={strokeWidth} />
27
25
  <circle
28
26
  cx={size / 2}
29
27
  cy={size / 2}
30
28
  r={radius}
31
- fill='none'
32
- stroke='currentColor'
33
- strokeOpacity={0.2}
29
+ fill="none"
30
+ stroke="currentColor"
34
31
  strokeWidth={strokeWidth}
35
- />
36
- <circle
37
- cx={size / 2}
38
- cy={size / 2}
39
- r={radius}
40
- fill='none'
41
- stroke='currentColor'
42
- strokeWidth={strokeWidth}
43
- strokeLinecap='round'
32
+ strokeLinecap="round"
44
33
  strokeDasharray={circumference}
45
34
  strokeDashoffset={circumference * (1 - clamped / 100)}
46
35
  />
@@ -8,38 +8,27 @@ export const Select = SelectPrimitive.Root
8
8
  export const SelectValue = SelectPrimitive.Value
9
9
  export const SelectItemText = SelectPrimitive.ItemText
10
10
 
11
- export const SelectTrigger: FunctionComponent<SelectPrimitive.Trigger.Props> = ({
12
- className,
13
- children,
14
- ...props
15
- }) => (
11
+ export const SelectTrigger: FunctionComponent<SelectPrimitive.Trigger.Props> = ({ className, children, ...props }) => (
16
12
  <SelectPrimitive.Trigger
17
- data-slot='select-trigger'
13
+ data-slot="select-trigger"
18
14
  className={cn(
19
15
  'inline-flex h-7 items-center justify-between gap-1.5 rounded-md border border-border bg-bg px-2 text-body-sm text-text',
20
16
  'transition-colors outline-none hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',
21
17
  'data-popup-open:border-border-strong disabled:pointer-events-none disabled:opacity-50',
22
18
  className,
23
19
  )}
24
- {...props}>
20
+ {...props}
21
+ >
25
22
  {children}
26
- <SelectPrimitive.Icon className='text-fg-4'>
27
- <ChevronsUpDown className='size-3.5' />
23
+ <SelectPrimitive.Icon className="text-fg-4">
24
+ <ChevronsUpDown className="size-3.5" />
28
25
  </SelectPrimitive.Icon>
29
26
  </SelectPrimitive.Trigger>
30
27
  )
31
28
 
32
29
  export const SelectContent: FunctionComponent<
33
- SelectPrimitive.Popup.Props &
34
- Pick<SelectPrimitive.Positioner.Props, 'align' | 'alignItemWithTrigger' | 'side' | 'sideOffset'>
35
- > = ({
36
- className,
37
- align = 'start',
38
- alignItemWithTrigger = false,
39
- side = 'bottom',
40
- sideOffset = 6,
41
- ...props
42
- }) => (
30
+ SelectPrimitive.Popup.Props & Pick<SelectPrimitive.Positioner.Props, 'align' | 'alignItemWithTrigger' | 'side' | 'sideOffset'>
31
+ > = ({ className, align = 'start', alignItemWithTrigger = false, side = 'bottom', sideOffset = 6, ...props }) => (
43
32
  <SelectPrimitive.Portal>
44
33
  <PortalScope>
45
34
  <SelectPrimitive.Positioner
@@ -47,9 +36,10 @@ export const SelectContent: FunctionComponent<
47
36
  alignItemWithTrigger={alignItemWithTrigger}
48
37
  side={side}
49
38
  sideOffset={sideOffset}
50
- className='isolate z-80 outline-none'>
39
+ className="isolate z-80 outline-none"
40
+ >
51
41
  <SelectPrimitive.Popup
52
- data-slot='select-content'
42
+ data-slot="select-content"
53
43
  className={cn(
54
44
  'max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto',
55
45
  'rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',
@@ -62,22 +52,19 @@ export const SelectContent: FunctionComponent<
62
52
  </SelectPrimitive.Portal>
63
53
  )
64
54
 
65
- export const SelectItem: FunctionComponent<SelectPrimitive.Item.Props> = ({
66
- className,
67
- children,
68
- ...props
69
- }) => (
55
+ export const SelectItem: FunctionComponent<SelectPrimitive.Item.Props> = ({ className, children, ...props }) => (
70
56
  <SelectPrimitive.Item
71
- data-slot='select-item'
57
+ data-slot="select-item"
72
58
  className={cn(
73
59
  'flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 text-body-sm text-text outline-none select-none',
74
60
  'data-highlighted:bg-surface-hover',
75
61
  className,
76
62
  )}
77
- {...props}>
78
- <span className='flex min-w-0 flex-1 flex-col gap-0.5'>{children}</span>
79
- <SelectPrimitive.ItemIndicator className='mt-0.5 text-fg-1'>
80
- <Check className='size-3.5' />
63
+ {...props}
64
+ >
65
+ <span className="flex min-w-0 flex-1 flex-col gap-0.5">{children}</span>
66
+ <SelectPrimitive.ItemIndicator className="mt-0.5 text-fg-1">
67
+ <Check className="size-3.5" />
81
68
  </SelectPrimitive.ItemIndicator>
82
69
  </SelectPrimitive.Item>
83
70
  )
@@ -2,11 +2,10 @@ import { Toaster as SonnerToaster, toast } from 'sonner'
2
2
 
3
3
  export { toast }
4
4
 
5
- /** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */
6
5
  export function Toaster() {
7
6
  return (
8
7
  <SonnerToaster
9
- position='bottom-right'
8
+ position="bottom-right"
10
9
  toastOptions={{
11
10
  style: {
12
11
  background: 'var(--surface)',
@@ -2,5 +2,5 @@ import { LoaderCircle } from 'lucide-react'
2
2
  import { cn } from '../../lib/utils.ts'
3
3
 
4
4
  export function Spinner({ className }: { className?: string }) {
5
- return <LoaderCircle aria-label='Loading' className={cn('size-4 animate-spin text-fg-3', className)} />
5
+ return <LoaderCircle aria-label="Loading" className={cn('size-4 animate-spin text-fg-3', className)} />
6
6
  }