tembro 4.0.1 → 4.2.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 (75) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/components/display/chat.cjs +1 -0
  3. package/dist/components/display/chat.d.ts +94 -0
  4. package/dist/components/display/chat.js +1 -0
  5. package/dist/components/display/index.d.ts +1 -0
  6. package/dist/components/display/kanban.d.ts +33 -1
  7. package/dist/components/inputs/combobox.cjs +1 -0
  8. package/dist/components/inputs/combobox.d.ts +46 -0
  9. package/dist/components/inputs/combobox.js +1 -0
  10. package/dist/components/inputs/index.d.ts +1 -0
  11. package/dist/components/inputs/public.d.ts +1 -0
  12. package/dist/components/layout/sidebar.d.ts +24 -2
  13. package/dist/components/modern/rich-text-editor.d.ts +13 -1
  14. package/dist/components/theme-provider.d.ts +19 -6
  15. package/dist/components/ui/badge/index.d.ts +8 -2
  16. package/dist/src/components/display/activity-feed.cjs +1 -1
  17. package/dist/src/components/display/activity-feed.js +4 -1
  18. package/dist/src/components/display/chat.cjs +1 -0
  19. package/dist/src/components/display/chat.js +372 -0
  20. package/dist/src/components/display/index.cjs +1 -1
  21. package/dist/src/components/display/index.js +17 -16
  22. package/dist/src/components/display/kanban.cjs +1 -1
  23. package/dist/src/components/display/kanban.js +321 -176
  24. package/dist/src/components/display/timeline.cjs +1 -1
  25. package/dist/src/components/display/timeline.js +5 -5
  26. package/dist/src/components/inputs/combobox.cjs +1 -0
  27. package/dist/src/components/inputs/combobox.js +162 -0
  28. package/dist/src/components/inputs/index.cjs +1 -1
  29. package/dist/src/components/inputs/index.js +10 -9
  30. package/dist/src/components/inputs/public.cjs +1 -1
  31. package/dist/src/components/inputs/public.js +10 -9
  32. package/dist/src/components/inputs/tag-input.cjs +1 -1
  33. package/dist/src/components/inputs/tag-input.js +6 -2
  34. package/dist/src/components/layout/index.cjs +1 -1
  35. package/dist/src/components/layout/index.js +4 -4
  36. package/dist/src/components/layout/public.cjs +1 -1
  37. package/dist/src/components/layout/public.js +4 -4
  38. package/dist/src/components/layout/sidebar.cjs +1 -1
  39. package/dist/src/components/layout/sidebar.js +326 -228
  40. package/dist/src/components/modern/rich-text-editor.cjs +1 -1
  41. package/dist/src/components/modern/rich-text-editor.js +216 -89
  42. package/dist/src/components/theme-provider.cjs +1 -1
  43. package/dist/src/components/theme-provider.js +58 -36
  44. package/dist/src/components/ui/badge/index.cjs +1 -1
  45. package/dist/src/components/ui/badge/index.js +59 -47
  46. package/dist/src/components/ui/input/clearable.cjs +1 -1
  47. package/dist/src/components/ui/input/clearable.js +5 -2
  48. package/dist/src/components/ui/input/primitive.cjs +1 -1
  49. package/dist/src/components/ui/input/primitive.js +1 -1
  50. package/dist/src/index.cjs +1 -1
  51. package/dist/src/index.js +88 -86
  52. package/dist/src/public-component-surface.cjs +1 -1
  53. package/dist/src/public-component-surface.js +107 -41
  54. package/package.json +1 -1
  55. package/packages/cli/dist/index.cjs +37 -37
  56. package/packages/cli/vendor/src/components/display/activity-feed.tsx +1 -1
  57. package/packages/cli/vendor/src/components/display/chat.tsx +237 -0
  58. package/packages/cli/vendor/src/components/display/index.ts +1 -0
  59. package/packages/cli/vendor/src/components/display/kanban.tsx +219 -72
  60. package/packages/cli/vendor/src/components/display/timeline.tsx +10 -10
  61. package/packages/cli/vendor/src/components/inputs/combobox.tsx +303 -0
  62. package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
  63. package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
  64. package/packages/cli/vendor/src/components/inputs/tag-input.tsx +3 -2
  65. package/packages/cli/vendor/src/components/layout/sidebar.tsx +212 -51
  66. package/packages/cli/vendor/src/components/modern/rich-text-editor.tsx +115 -32
  67. package/packages/cli/vendor/src/components/theme-provider.tsx +82 -36
  68. package/packages/cli/vendor/src/components/ui/badge/index.tsx +29 -8
  69. package/packages/cli/vendor/src/components/ui/input/clearable.tsx +2 -2
  70. package/packages/cli/vendor/src/components/ui/input/primitive.tsx +1 -1
  71. package/packages/cli/vendor/src/public-component-surface.ts +36 -22
  72. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
  73. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
  74. package/packages/cli/vendor/templates/styles/globals.css +91 -10
  75. package/registry.json +28 -11
@@ -0,0 +1,303 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { CheckIcon, ChevronsUpDownIcon, SearchIcon, XIcon } from "lucide-react"
5
+
6
+ import { Button } from "@/components/ui/button"
7
+ import { Input } from "@/components/ui/input"
8
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
9
+ import { cn, stopInteractivePropagation } from "@/lib/utils"
10
+
11
+ export type ComboboxOption<TValue extends string = string, TData = unknown> = {
12
+ value: TValue
13
+ label: React.ReactNode
14
+ description?: React.ReactNode
15
+ disabled?: boolean
16
+ disabledReason?: React.ReactNode
17
+ keywords?: string[]
18
+ data?: TData
19
+ }
20
+
21
+ export type ComboboxGroup<TValue extends string = string, TData = unknown> = {
22
+ label?: React.ReactNode
23
+ options: ComboboxOption<TValue, TData>[]
24
+ }
25
+
26
+ export type ComboboxLabels = {
27
+ placeholder?: string
28
+ searchPlaceholder?: string
29
+ empty?: string
30
+ clear?: string
31
+ }
32
+
33
+ export type ComboboxProps<TValue extends string = string, TData = unknown> = Omit<React.ComponentProps<"div">, "onChange"> & {
34
+ value?: TValue
35
+ defaultValue?: TValue
36
+ onValueChange?: (value: TValue | undefined, option?: ComboboxOption<TValue, TData>) => void
37
+ options?: ComboboxOption<TValue, TData>[]
38
+ groups?: ComboboxGroup<TValue, TData>[]
39
+ disabled?: boolean
40
+ clearable?: boolean
41
+ searchable?: boolean
42
+ open?: boolean
43
+ defaultOpen?: boolean
44
+ onOpenChange?: (open: boolean) => void
45
+ labels?: ComboboxLabels
46
+ invalid?: boolean
47
+ renderOption?: (option: ComboboxOption<TValue, TData>, state: { selected: boolean }) => React.ReactNode
48
+ renderValue?: (option: ComboboxOption<TValue, TData>) => React.ReactNode
49
+ filterOption?: (option: ComboboxOption<TValue, TData>, search: string) => boolean
50
+ triggerClassName?: string
51
+ contentClassName?: string
52
+ searchClassName?: string
53
+ optionClassName?: string
54
+ }
55
+
56
+ function useControllableState<TValue>({
57
+ value,
58
+ defaultValue,
59
+ onChange,
60
+ }: {
61
+ value?: TValue
62
+ defaultValue: TValue
63
+ onChange?: (value: TValue) => void
64
+ }) {
65
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
66
+ const isControlled = value !== undefined
67
+ const currentValue = isControlled ? value : internalValue
68
+
69
+ const setValue = React.useCallback(
70
+ (nextValue: TValue) => {
71
+ if (!isControlled) {
72
+ setInternalValue(nextValue)
73
+ }
74
+ onChange?.(nextValue)
75
+ },
76
+ [isControlled, onChange]
77
+ )
78
+
79
+ return [currentValue, setValue] as const
80
+ }
81
+
82
+ function normalizeComboboxGroups<TValue extends string, TData>({
83
+ options,
84
+ groups,
85
+ }: Pick<ComboboxProps<TValue, TData>, "options" | "groups">) {
86
+ if (groups?.length) return groups
87
+ if (options?.length) return [{ options }]
88
+ return []
89
+ }
90
+
91
+ function getComboboxOptionText(option: ComboboxOption) {
92
+ return [
93
+ option.value,
94
+ typeof option.label === "string" || typeof option.label === "number" ? String(option.label) : "",
95
+ typeof option.description === "string" || typeof option.description === "number" ? String(option.description) : "",
96
+ ...(option.keywords ?? []),
97
+ ]
98
+ .join(" ")
99
+ .toLowerCase()
100
+ }
101
+
102
+ function defaultFilterOption(option: ComboboxOption, search: string) {
103
+ if (!search.trim()) return true
104
+ return getComboboxOptionText(option).includes(search.trim().toLowerCase())
105
+ }
106
+
107
+ function findOption<TValue extends string, TData>(
108
+ groups: ComboboxGroup<TValue, TData>[],
109
+ value?: TValue
110
+ ) {
111
+ if (!value) return undefined
112
+
113
+ for (const group of groups) {
114
+ const option = group.options.find((item) => item.value === value)
115
+ if (option) return option
116
+ }
117
+
118
+ return undefined
119
+ }
120
+
121
+ function Combobox<TValue extends string = string, TData = unknown>({
122
+ className,
123
+ value,
124
+ defaultValue,
125
+ onValueChange,
126
+ options,
127
+ groups,
128
+ disabled = false,
129
+ clearable = true,
130
+ searchable = true,
131
+ open,
132
+ defaultOpen = false,
133
+ onOpenChange,
134
+ labels,
135
+ invalid,
136
+ renderOption,
137
+ renderValue,
138
+ filterOption = defaultFilterOption,
139
+ triggerClassName,
140
+ contentClassName,
141
+ searchClassName,
142
+ optionClassName,
143
+ ...props
144
+ }: ComboboxProps<TValue, TData>) {
145
+ const normalizedGroups = React.useMemo(() => normalizeComboboxGroups({ options, groups }), [groups, options])
146
+ const [currentValue, setCurrentValue] = useControllableState<TValue | undefined>({
147
+ value,
148
+ defaultValue,
149
+ onChange: (nextValue) => onValueChange?.(nextValue, findOption(normalizedGroups, nextValue)),
150
+ })
151
+ const [isOpen, setIsOpen] = useControllableState({
152
+ value: open,
153
+ defaultValue: defaultOpen,
154
+ onChange: onOpenChange,
155
+ })
156
+ const [search, setSearch] = React.useState("")
157
+ const selectedOption = findOption(normalizedGroups, currentValue)
158
+ const filteredGroups = React.useMemo(
159
+ () =>
160
+ normalizedGroups
161
+ .map((group) => ({
162
+ ...group,
163
+ options: group.options.filter((option) => filterOption(option, search)),
164
+ }))
165
+ .filter((group) => group.options.length > 0),
166
+ [filterOption, normalizedGroups, search]
167
+ )
168
+ const hasMatches = filteredGroups.some((group) => group.options.length > 0)
169
+
170
+ const selectOption = (option: ComboboxOption<TValue, TData>) => {
171
+ if (option.disabled) return
172
+
173
+ setCurrentValue(option.value)
174
+ setIsOpen(false)
175
+ setSearch("")
176
+ }
177
+
178
+ const clearSelection = () => {
179
+ setCurrentValue(undefined)
180
+ setSearch("")
181
+ }
182
+
183
+ const handleClear = (event: React.MouseEvent<HTMLElement>) => {
184
+ stopInteractivePropagation(event)
185
+ clearSelection()
186
+ }
187
+
188
+ return (
189
+ <div data-slot="combobox" className={cn("w-full", className)} {...props}>
190
+ <Popover open={isOpen} onOpenChange={setIsOpen}>
191
+ <PopoverTrigger
192
+ render={
193
+ <Button
194
+ type="button"
195
+ variant="outline"
196
+ disabled={disabled}
197
+ aria-expanded={isOpen}
198
+ aria-invalid={invalid || undefined}
199
+ data-slot="combobox-trigger"
200
+ className={cn("w-full justify-between text-left", triggerClassName)}
201
+ />
202
+ }
203
+ >
204
+ <span className="min-w-0 flex-1 truncate">
205
+ {selectedOption ? renderValue?.(selectedOption) ?? selectedOption.label : (
206
+ <span className="text-muted-foreground">{labels?.placeholder ?? "Select option"}</span>
207
+ )}
208
+ </span>
209
+ <span className="ml-2 inline-flex shrink-0 items-center gap-1">
210
+ {clearable && currentValue && !disabled ? (
211
+ <span
212
+ role="button"
213
+ tabIndex={0}
214
+ data-slot="combobox-clear"
215
+ aria-label={labels?.clear ?? "Clear selection"}
216
+ onClick={handleClear}
217
+ onKeyDown={(event) => {
218
+ if (event.key !== "Enter" && event.key !== " ") return
219
+ event.preventDefault()
220
+ stopInteractivePropagation(event)
221
+ clearSelection()
222
+ }}
223
+ >
224
+ <XIcon data-icon="clear" />
225
+ </span>
226
+ ) : null}
227
+ <ChevronsUpDownIcon className="size-4 opacity-60" />
228
+ </span>
229
+ </PopoverTrigger>
230
+ <PopoverContent align="start" data-slot="combobox-content" className={cn("w-(--anchor-width) min-w-72 gap-2 p-2", contentClassName)}>
231
+ {searchable ? (
232
+ <div data-slot="combobox-search-wrap" className="relative">
233
+ <SearchIcon className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
234
+ <Input
235
+ data-slot="combobox-search"
236
+ value={search}
237
+ onChange={(event) => setSearch(event.currentTarget.value)}
238
+ placeholder={labels?.searchPlaceholder ?? "Search..."}
239
+ className={cn("pl-9", searchClassName)}
240
+ autoFocus
241
+ />
242
+ </div>
243
+ ) : null}
244
+
245
+ <div data-slot="combobox-list" className="flex max-h-72 flex-col gap-1 overflow-y-auto pr-1">
246
+ {!hasMatches ? (
247
+ <div data-slot="combobox-empty" className="rounded-[var(--radius-md)] px-3 py-6 text-center text-sm text-muted-foreground">
248
+ {labels?.empty ?? "No options found"}
249
+ </div>
250
+ ) : null}
251
+
252
+ {filteredGroups.map((group, groupIndex) => (
253
+ <div key={groupIndex} data-slot="combobox-group">
254
+ {group.label ? (
255
+ <div data-slot="combobox-group-label" className="sticky top-0 z-10 bg-popover px-2 py-1.5 text-xs font-medium text-muted-foreground">
256
+ {group.label}
257
+ </div>
258
+ ) : null}
259
+ {group.options.map((option) => {
260
+ const selected = option.value === currentValue
261
+
262
+ return (
263
+ <button
264
+ key={option.value}
265
+ type="button"
266
+ disabled={option.disabled}
267
+ data-slot="combobox-option"
268
+ data-selected={selected || undefined}
269
+ data-disabled={option.disabled || undefined}
270
+ className={cn(
271
+ "flex w-full items-start gap-2.5 rounded-[var(--radius-md)] px-2.5 py-2 text-left text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[selected]:bg-accent/70",
272
+ optionClassName
273
+ )}
274
+ onClick={() => selectOption(option)}
275
+ >
276
+ <span className="mt-0.5 flex size-4 shrink-0 items-center justify-center">
277
+ {selected ? <CheckIcon className="size-4" /> : null}
278
+ </span>
279
+ <span className="min-w-0 flex-1">
280
+ {renderOption?.(option, { selected }) ?? (
281
+ <span className="flex min-w-0 flex-col">
282
+ <span className="truncate font-medium">{option.label}</span>
283
+ {(option.description || option.disabledReason) ? (
284
+ <span className="truncate text-xs text-muted-foreground">
285
+ {option.disabled ? option.disabledReason ?? option.description : option.description}
286
+ </span>
287
+ ) : null}
288
+ </span>
289
+ )}
290
+ </span>
291
+ </button>
292
+ )
293
+ })}
294
+ </div>
295
+ ))}
296
+ </div>
297
+ </PopoverContent>
298
+ </Popover>
299
+ </div>
300
+ )
301
+ }
302
+
303
+ export { Combobox }
@@ -1,4 +1,5 @@
1
1
  export * from "./async-select"
2
+ export * from "./combobox"
2
3
  export * from "./otp-input"
3
4
  export * from "./rating"
4
5
  export * from "./json-input"
@@ -1,4 +1,5 @@
1
1
  export * from "./async-select"
2
+ export * from "./combobox"
2
3
  export * from "./otp-input"
3
4
  export * from "./rating"
4
5
  export * from "./slider"
@@ -86,7 +86,8 @@ function TagInput({
86
86
  {!readOnly && !disabled && (
87
87
  <button
88
88
  type="button"
89
- className="inline-flex size-5 items-center justify-center rounded-full text-muted-foreground transition hover:bg-[color:var(--aui-control-bg)] hover:text-foreground"
89
+ className="grid size-5 shrink-0 place-items-center rounded-full p-0 leading-none text-muted-foreground transition hover:bg-[color:var(--aui-control-bg)] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40"
90
+ aria-label={`Remove ${tag}`}
90
91
  onClick={(event) => {
91
92
  stopInteractivePropagation(event)
92
93
  removeTag(tag)
@@ -94,7 +95,7 @@ function TagInput({
94
95
  onMouseDown={stopInteractivePropagation}
95
96
  onDoubleClick={stopInteractivePropagation}
96
97
  >
97
- <XIcon className="size-3" />
98
+ <XIcon className="size-3" strokeWidth={2} />
98
99
  </button>
99
100
  )}
100
101
  </Badge>