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.
- package/CHANGELOG.md +15 -0
- package/dist/components/display/chat.cjs +1 -0
- package/dist/components/display/chat.d.ts +94 -0
- package/dist/components/display/chat.js +1 -0
- package/dist/components/display/index.d.ts +1 -0
- package/dist/components/display/kanban.d.ts +33 -1
- package/dist/components/inputs/combobox.cjs +1 -0
- package/dist/components/inputs/combobox.d.ts +46 -0
- package/dist/components/inputs/combobox.js +1 -0
- package/dist/components/inputs/index.d.ts +1 -0
- package/dist/components/inputs/public.d.ts +1 -0
- package/dist/components/layout/sidebar.d.ts +24 -2
- package/dist/components/modern/rich-text-editor.d.ts +13 -1
- package/dist/components/theme-provider.d.ts +19 -6
- package/dist/components/ui/badge/index.d.ts +8 -2
- package/dist/src/components/display/activity-feed.cjs +1 -1
- package/dist/src/components/display/activity-feed.js +4 -1
- package/dist/src/components/display/chat.cjs +1 -0
- package/dist/src/components/display/chat.js +372 -0
- package/dist/src/components/display/index.cjs +1 -1
- package/dist/src/components/display/index.js +17 -16
- package/dist/src/components/display/kanban.cjs +1 -1
- package/dist/src/components/display/kanban.js +321 -176
- package/dist/src/components/display/timeline.cjs +1 -1
- package/dist/src/components/display/timeline.js +5 -5
- package/dist/src/components/inputs/combobox.cjs +1 -0
- package/dist/src/components/inputs/combobox.js +162 -0
- package/dist/src/components/inputs/index.cjs +1 -1
- package/dist/src/components/inputs/index.js +10 -9
- package/dist/src/components/inputs/public.cjs +1 -1
- package/dist/src/components/inputs/public.js +10 -9
- package/dist/src/components/inputs/tag-input.cjs +1 -1
- package/dist/src/components/inputs/tag-input.js +6 -2
- package/dist/src/components/layout/index.cjs +1 -1
- package/dist/src/components/layout/index.js +4 -4
- package/dist/src/components/layout/public.cjs +1 -1
- package/dist/src/components/layout/public.js +4 -4
- package/dist/src/components/layout/sidebar.cjs +1 -1
- package/dist/src/components/layout/sidebar.js +326 -228
- package/dist/src/components/modern/rich-text-editor.cjs +1 -1
- package/dist/src/components/modern/rich-text-editor.js +216 -89
- package/dist/src/components/theme-provider.cjs +1 -1
- package/dist/src/components/theme-provider.js +58 -36
- package/dist/src/components/ui/badge/index.cjs +1 -1
- package/dist/src/components/ui/badge/index.js +59 -47
- package/dist/src/components/ui/input/clearable.cjs +1 -1
- package/dist/src/components/ui/input/clearable.js +5 -2
- package/dist/src/components/ui/input/primitive.cjs +1 -1
- package/dist/src/components/ui/input/primitive.js +1 -1
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +88 -86
- package/dist/src/public-component-surface.cjs +1 -1
- package/dist/src/public-component-surface.js +107 -41
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +37 -37
- package/packages/cli/vendor/src/components/display/activity-feed.tsx +1 -1
- package/packages/cli/vendor/src/components/display/chat.tsx +237 -0
- package/packages/cli/vendor/src/components/display/index.ts +1 -0
- package/packages/cli/vendor/src/components/display/kanban.tsx +219 -72
- package/packages/cli/vendor/src/components/display/timeline.tsx +10 -10
- package/packages/cli/vendor/src/components/inputs/combobox.tsx +303 -0
- package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/tag-input.tsx +3 -2
- package/packages/cli/vendor/src/components/layout/sidebar.tsx +212 -51
- package/packages/cli/vendor/src/components/modern/rich-text-editor.tsx +115 -32
- package/packages/cli/vendor/src/components/theme-provider.tsx +82 -36
- package/packages/cli/vendor/src/components/ui/badge/index.tsx +29 -8
- package/packages/cli/vendor/src/components/ui/input/clearable.tsx +2 -2
- package/packages/cli/vendor/src/components/ui/input/primitive.tsx +1 -1
- package/packages/cli/vendor/src/public-component-surface.ts +36 -22
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
- package/packages/cli/vendor/templates/styles/globals.css +91 -10
- 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 }
|
|
@@ -86,7 +86,8 @@ function TagInput({
|
|
|
86
86
|
{!readOnly && !disabled && (
|
|
87
87
|
<button
|
|
88
88
|
type="button"
|
|
89
|
-
className="
|
|
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>
|