@arcote.tech/arc-ds 0.5.2 → 0.5.6

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.
@@ -10,33 +10,67 @@ export interface SearchSelectOption {
10
10
  icon?: ReactNode;
11
11
  }
12
12
 
13
- export interface SearchSelectProps {
14
- value?: string;
15
- onChange?: (value: string) => void;
13
+ export interface SearchSelectTriggerProps {
14
+ selectedOption: SearchSelectOption | undefined;
15
+ placeholder: string;
16
+ open: () => void;
17
+ }
18
+
19
+ export interface SearchSelectMultiTriggerProps {
20
+ selectedOptions: SearchSelectOption[];
21
+ placeholder: string;
22
+ open: () => void;
23
+ }
24
+
25
+ // ─── Props union ───────────────────────────────────────────────
26
+
27
+ interface SearchSelectBaseProps {
16
28
  options: SearchSelectOption[];
17
29
  placeholder?: string;
18
30
  searchPlaceholder?: string;
19
31
  position?: "relative" | "absolute";
20
- /** Direction the dropdown opens. Default: "down". */
21
32
  direction?: "down" | "up";
22
- /** Trigger button size. Default: "default". */
23
33
  size?: "default" | "sm";
24
- renderOption?: (option: SearchSelectOption, isActive: boolean, isSelected: boolean) => ReactNode;
34
+ renderOption?: (
35
+ option: SearchSelectOption,
36
+ isActive: boolean,
37
+ isSelected: boolean,
38
+ ) => ReactNode;
25
39
  allowClear?: boolean;
26
40
  }
27
41
 
28
- export function SearchSelect({
29
- value,
30
- onChange,
31
- options,
32
- placeholder = "Wybierz...",
33
- searchPlaceholder = "Szukaj...",
34
- position = "relative",
35
- direction = "down",
36
- size = "default",
37
- renderOption,
38
- allowClear = true,
39
- }: SearchSelectProps) {
42
+ interface SearchSelectSingleProps extends SearchSelectBaseProps {
43
+ multiple?: false;
44
+ value?: string;
45
+ onChange?: (value: string) => void;
46
+ renderTrigger?: (props: SearchSelectTriggerProps) => ReactNode;
47
+ }
48
+
49
+ interface SearchSelectMultiProps extends SearchSelectBaseProps {
50
+ multiple: true;
51
+ value?: string[];
52
+ onChange?: (value: string[]) => void;
53
+ renderTrigger?: (props: SearchSelectMultiTriggerProps) => ReactNode;
54
+ }
55
+
56
+ export type SearchSelectProps = SearchSelectSingleProps | SearchSelectMultiProps;
57
+
58
+ // ─── Component ─────────────────────────────────────────────────
59
+
60
+ export function SearchSelect(props: SearchSelectProps) {
61
+ const {
62
+ options,
63
+ placeholder = "Wybierz...",
64
+ searchPlaceholder = "Szukaj...",
65
+ position = "relative",
66
+ direction = "down",
67
+ size = "default",
68
+ renderOption,
69
+ allowClear = true,
70
+ } = props;
71
+
72
+ const isMulti = props.multiple === true;
73
+
40
74
  const [isOpen, setIsOpen] = useState(false);
41
75
  const [query, setQuery] = useState("");
42
76
  const [activeIndex, setActiveIndex] = useState(0);
@@ -44,11 +78,29 @@ export function SearchSelect({
44
78
  const containerRef = useRef<HTMLDivElement>(null);
45
79
  const inputRef = useRef<HTMLInputElement>(null);
46
80
 
81
+ // ─── Value helpers ──────────────────────────────────────────
82
+
83
+ const singleValue = !isMulti ? (props.value ?? "") : "";
84
+ const multiValue = isMulti ? (props.value ?? []) : [];
85
+
47
86
  const selectedOption = useMemo(
48
- () => options.find((o) => o.value === value),
49
- [options, value],
87
+ () => (!isMulti ? options.find((o) => o.value === singleValue) : undefined),
88
+ [options, singleValue, isMulti],
89
+ );
90
+
91
+ const selectedOptions = useMemo(
92
+ () => (isMulti ? options.filter((o) => multiValue.includes(o.value)) : []),
93
+ [options, multiValue, isMulti],
94
+ );
95
+
96
+ const isSelected = useCallback(
97
+ (val: string) =>
98
+ isMulti ? multiValue.includes(val) : singleValue === val,
99
+ [isMulti, singleValue, multiValue],
50
100
  );
51
101
 
102
+ // ─── Filtering ──────────────────────────────────────────────
103
+
52
104
  const filtered = useMemo(() => {
53
105
  if (!query.trim()) return options;
54
106
  const q = query.toLowerCase().trim();
@@ -56,14 +108,19 @@ export function SearchSelect({
56
108
  }, [options, query]);
57
109
 
58
110
  useEffect(() => {
59
- setActiveIndex((prev) => (filtered.length === 0 ? 0 : Math.min(prev, filtered.length - 1)));
111
+ setActiveIndex((prev) =>
112
+ filtered.length === 0 ? 0 : Math.min(prev, filtered.length - 1),
113
+ );
60
114
  }, [filtered.length]);
61
115
 
62
116
  // Click outside
63
117
  useEffect(() => {
64
118
  if (!isOpen) return;
65
119
  const handler = (e: MouseEvent) => {
66
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
120
+ if (
121
+ containerRef.current &&
122
+ !containerRef.current.contains(e.target as Node)
123
+ ) {
67
124
  setIsOpen(false);
68
125
  setQuery("");
69
126
  }
@@ -79,42 +136,81 @@ export function SearchSelect({
79
136
  if (el) el.scrollIntoView({ block: "nearest" });
80
137
  }, [activeIndex, isOpen]);
81
138
 
82
- const select = useCallback(
139
+ // ─── Actions ────────────────────────────────────────────────
140
+
141
+ const selectSingle = useCallback(
142
+ (val: string) => {
143
+ if (!isMulti) {
144
+ (props as SearchSelectSingleProps).onChange?.(val);
145
+ setIsOpen(false);
146
+ setQuery("");
147
+ }
148
+ },
149
+ [isMulti, props],
150
+ );
151
+
152
+ const toggleMulti = useCallback(
153
+ (val: string) => {
154
+ if (isMulti) {
155
+ const current = (props as SearchSelectMultiProps).value ?? [];
156
+ const next = current.includes(val)
157
+ ? current.filter((v) => v !== val)
158
+ : [...current, val];
159
+ (props as SearchSelectMultiProps).onChange?.(next);
160
+ // Keep dropdown open in multi mode
161
+ }
162
+ },
163
+ [isMulti, props],
164
+ );
165
+
166
+ const handleOptionClick = useCallback(
83
167
  (val: string) => {
84
- onChange?.(val);
85
- setIsOpen(false);
86
- setQuery("");
168
+ if (isMulti) {
169
+ toggleMulti(val);
170
+ } else {
171
+ selectSingle(val);
172
+ }
87
173
  },
88
- [onChange],
174
+ [isMulti, selectSingle, toggleMulti],
89
175
  );
90
176
 
91
177
  const clear = useCallback(() => {
92
- onChange?.("");
178
+ if (isMulti) {
179
+ (props as SearchSelectMultiProps).onChange?.([]);
180
+ } else {
181
+ (props as SearchSelectSingleProps).onChange?.("");
182
+ }
93
183
  setIsOpen(false);
94
184
  setQuery("");
95
- }, [onChange]);
185
+ }, [isMulti, props]);
96
186
 
97
- const open = () => {
187
+ const open = useCallback(() => {
98
188
  setIsOpen(true);
99
189
  setActiveIndex(0);
100
190
  setQuery("");
101
191
  requestAnimationFrame(() => inputRef.current?.focus());
102
- };
192
+ }, []);
103
193
 
104
194
  const handleKeyDown = (e: React.KeyboardEvent) => {
105
195
  switch (e.key) {
106
196
  case "ArrowDown":
107
197
  e.preventDefault();
108
- setActiveIndex((prev) => (filtered.length === 0 ? 0 : (prev + 1) % filtered.length));
198
+ setActiveIndex((prev) =>
199
+ filtered.length === 0 ? 0 : (prev + 1) % filtered.length,
200
+ );
109
201
  break;
110
202
  case "ArrowUp":
111
203
  e.preventDefault();
112
- setActiveIndex((prev) => (filtered.length === 0 ? 0 : (prev - 1 + filtered.length) % filtered.length));
204
+ setActiveIndex((prev) =>
205
+ filtered.length === 0
206
+ ? 0
207
+ : (prev - 1 + filtered.length) % filtered.length,
208
+ );
113
209
  break;
114
210
  case "Enter":
115
211
  e.preventDefault();
116
212
  if (filtered.length > 0 && activeIndex < filtered.length) {
117
- select(filtered[activeIndex].value);
213
+ handleOptionClick(filtered[activeIndex].value);
118
214
  }
119
215
  break;
120
216
  case "Escape":
@@ -125,13 +221,23 @@ export function SearchSelect({
125
221
  }
126
222
  };
127
223
 
128
- const defaultRenderOption = (opt: SearchSelectOption, isActive: boolean, isSelected: boolean) => (
224
+ // ─── Render helpers ─────────────────────────────────────────
225
+
226
+ const defaultRenderOption = (
227
+ opt: SearchSelectOption,
228
+ isActive: boolean,
229
+ isSel: boolean,
230
+ ) => (
129
231
  <div className="flex items-center gap-2.5">
130
- {opt.icon && <span className="shrink-0 text-muted-foreground">{opt.icon}</span>}
131
- <span className={`flex-1 truncate text-sm ${isActive ? "text-primary font-medium" : ""}`}>
232
+ {opt.icon && (
233
+ <span className="shrink-0 text-muted-foreground">{opt.icon}</span>
234
+ )}
235
+ <span
236
+ className={`flex-1 truncate text-sm ${isActive ? "font-medium text-primary" : ""}`}
237
+ >
132
238
  {opt.label}
133
239
  </span>
134
- {isSelected && <Check className="h-3.5 w-3.5 text-primary shrink-0" />}
240
+ {isSel && <Check className="h-3.5 w-3.5 shrink-0 text-primary" />}
135
241
  </div>
136
242
  );
137
243
 
@@ -139,100 +245,174 @@ export function SearchSelect({
139
245
 
140
246
  const isAbsolute = position === "absolute";
141
247
  const isUp = direction === "up";
142
- const triggerHeight = size === "sm" ? "h-8 text-xs px-2.5" : "h-10 md:h-9 text-base md:text-sm px-3";
248
+ const triggerHeight =
249
+ size === "sm" ? "h-8 text-xs px-2.5" : "h-10 md:h-9 text-base md:text-sm px-3";
250
+
251
+ const hasValue = isMulti ? multiValue.length > 0 : !!singleValue;
252
+
253
+ // ─── Trigger ────────────────────────────────────────────────
254
+
255
+ const renderDefaultTrigger = () => (
256
+ <button
257
+ type="button"
258
+ onClick={open}
259
+ className={`flex w-full items-center justify-between rounded-md border border-input bg-transparent ${triggerHeight} shadow-xs transition-colors hover:bg-muted/50`}
260
+ >
261
+ {isMulti ? (
262
+ selectedOptions.length > 0 ? (
263
+ <span className="truncate">
264
+ {selectedOptions.map((o) => o.label).join(", ")}
265
+ </span>
266
+ ) : (
267
+ <span className="text-muted-foreground">{placeholder}</span>
268
+ )
269
+ ) : selectedOption ? (
270
+ <div className="flex min-w-0 items-center gap-2">
271
+ {selectedOption.icon && (
272
+ <span className="shrink-0">{selectedOption.icon}</span>
273
+ )}
274
+ <span className="truncate">{selectedOption.label}</span>
275
+ </div>
276
+ ) : (
277
+ <span className="text-muted-foreground">{placeholder}</span>
278
+ )}
279
+ <div className="flex shrink-0 items-center gap-1">
280
+ {allowClear && hasValue && (
281
+ <span
282
+ role="button"
283
+ onClick={(e) => {
284
+ e.stopPropagation();
285
+ clear();
286
+ }}
287
+ className="rounded-full p-0.5 text-muted-foreground/50 transition-colors hover:bg-muted hover:text-foreground"
288
+ >
289
+ <X className="h-3 w-3" />
290
+ </span>
291
+ )}
292
+ <ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
293
+ </div>
294
+ </button>
295
+ );
296
+
297
+ const renderCustomTrigger = () => {
298
+ if (isMulti && (props as SearchSelectMultiProps).renderTrigger) {
299
+ return (props as SearchSelectMultiProps).renderTrigger!({
300
+ selectedOptions,
301
+ placeholder,
302
+ open,
303
+ });
304
+ }
305
+ if (!isMulti && (props as SearchSelectSingleProps).renderTrigger) {
306
+ return (props as SearchSelectSingleProps).renderTrigger!({
307
+ selectedOption,
308
+ placeholder,
309
+ open,
310
+ });
311
+ }
312
+ return null;
313
+ };
314
+
315
+ const hasCustomTrigger = isMulti
316
+ ? !!(props as SearchSelectMultiProps).renderTrigger
317
+ : !!(props as SearchSelectSingleProps).renderTrigger;
318
+
319
+ // ─── Option list (shared between relative and absolute) ─────
320
+
321
+ const renderOptionList = () => (
322
+ <>
323
+ {filtered.length > 0 && (
324
+ <div className="max-h-[192px] overflow-y-auto border-t border-border">
325
+ {filtered.map((opt, i) => (
326
+ <button
327
+ key={opt.value}
328
+ type="button"
329
+ data-active={i === activeIndex}
330
+ onClick={() => handleOptionClick(opt.value)}
331
+ onMouseEnter={() => setActiveIndex(i)}
332
+ className={`flex w-full items-center px-3 py-2 text-left transition-colors ${
333
+ i === activeIndex ? "bg-primary/10" : "hover:bg-muted"
334
+ }`}
335
+ >
336
+ {optionRenderer(opt, i === activeIndex, isSelected(opt.value))}
337
+ </button>
338
+ ))}
339
+ </div>
340
+ )}
341
+ {filtered.length === 0 && query && (
342
+ <div className="border-t border-border px-3 py-3 text-center text-xs text-muted-foreground">
343
+ Brak wyników
344
+ </div>
345
+ )}
346
+ </>
347
+ );
348
+
349
+ const renderSearchInput = () => (
350
+ <div className="p-1.5">
351
+ <Input
352
+ ref={inputRef}
353
+ icon={Search}
354
+ size="sm"
355
+ value={query}
356
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
357
+ setQuery(e.target.value);
358
+ setActiveIndex(0);
359
+ }}
360
+ onKeyDown={handleKeyDown}
361
+ placeholder={searchPlaceholder}
362
+ className="border-0 shadow-none focus-visible:border-transparent focus-visible:ring-0"
363
+ />
364
+ </div>
365
+ );
143
366
 
144
367
  return (
145
368
  <div ref={containerRef} className={isAbsolute ? "relative" : ""}>
146
369
  {/* Trigger */}
147
370
  {!isOpen ? (
148
- <button
149
- type="button"
150
- onClick={open}
151
- className={`flex w-full items-center justify-between rounded-md border border-input bg-transparent ${triggerHeight} shadow-xs transition-colors hover:bg-muted/50`}
152
- >
153
- {selectedOption ? (
154
- <div className="flex items-center gap-2 min-w-0">
155
- {selectedOption.icon && <span className="shrink-0">{selectedOption.icon}</span>}
156
- <span className="truncate">{selectedOption.label}</span>
157
- </div>
158
- ) : (
159
- <span className="text-muted-foreground">{placeholder}</span>
160
- )}
161
- <div className="flex items-center gap-1 shrink-0">
162
- {allowClear && value && (
163
- <span
164
- role="button"
165
- onClick={(e) => {
166
- e.stopPropagation();
167
- clear();
168
- }}
169
- className="rounded-full p-0.5 text-muted-foreground/50 hover:bg-muted hover:text-foreground transition-colors"
170
- >
171
- <X className="h-3 w-3" />
172
- </span>
173
- )}
174
- <ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
175
- </div>
176
- </button>
371
+ hasCustomTrigger ? (
372
+ renderCustomTrigger()
373
+ ) : (
374
+ renderDefaultTrigger()
375
+ )
177
376
  ) : !isAbsolute ? (
178
377
  /* Inline search — relative mode */
179
- <div className="rounded-md border border-input overflow-hidden">
180
- <div className="p-1.5">
181
- <Input
182
- ref={inputRef}
183
- icon={Search}
184
- size="sm"
185
- value={query}
186
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
187
- setQuery(e.target.value);
188
- setActiveIndex(0);
189
- }}
190
- onKeyDown={handleKeyDown}
191
- placeholder={searchPlaceholder}
192
- className="border-0 shadow-none focus-visible:ring-0 focus-visible:border-transparent"
193
- />
194
- </div>
195
- {filtered.length > 0 && (
196
- <div className="border-t border-border max-h-[192px] overflow-y-auto">
197
- {filtered.map((opt, i) => (
198
- <button
199
- key={opt.value}
200
- type="button"
201
- data-active={i === activeIndex}
202
- onClick={() => select(opt.value)}
203
- onMouseEnter={() => setActiveIndex(i)}
204
- className={`flex w-full items-center px-3 py-2 text-left transition-colors ${
205
- i === activeIndex ? "bg-primary/10" : "hover:bg-muted"
206
- }`}
207
- >
208
- {optionRenderer(opt, i === activeIndex, opt.value === value)}
209
- </button>
210
- ))}
211
- </div>
212
- )}
213
- {filtered.length === 0 && query && (
214
- <div className="border-t border-border px-3 py-3 text-center text-xs text-muted-foreground">
215
- Brak wyników
216
- </div>
217
- )}
378
+ <div className="overflow-hidden rounded-md border border-input">
379
+ {renderSearchInput()}
380
+ {renderOptionList()}
218
381
  </div>
219
382
  ) : (
220
383
  /* Trigger stays visible in absolute mode */
221
- <button
222
- type="button"
223
- onClick={() => { setIsOpen(false); setQuery(""); }}
224
- className={`flex w-full items-center justify-between rounded-md border border-ring bg-transparent ${triggerHeight} shadow-xs ring-[3px] ring-ring/50`}
225
- >
226
- {selectedOption ? (
227
- <div className="flex items-center gap-2 min-w-0">
228
- {selectedOption.icon && <span className="shrink-0">{selectedOption.icon}</span>}
229
- <span className="truncate">{selectedOption.label}</span>
230
- </div>
231
- ) : (
232
- <span className="text-muted-foreground">{placeholder}</span>
233
- )}
234
- <ChevronDown className="h-3.5 w-3.5 text-muted-foreground rotate-180 transition-transform" />
235
- </button>
384
+ hasCustomTrigger ? (
385
+ renderCustomTrigger()
386
+ ) : (
387
+ <button
388
+ type="button"
389
+ onClick={() => {
390
+ setIsOpen(false);
391
+ setQuery("");
392
+ }}
393
+ className={`flex w-full items-center justify-between rounded-md border border-ring bg-transparent ${triggerHeight} shadow-xs ring-[3px] ring-ring/50`}
394
+ >
395
+ {isMulti ? (
396
+ selectedOptions.length > 0 ? (
397
+ <span className="truncate">
398
+ {selectedOptions.map((o) => o.label).join(", ")}
399
+ </span>
400
+ ) : (
401
+ <span className="text-muted-foreground">{placeholder}</span>
402
+ )
403
+ ) : selectedOption ? (
404
+ <div className="flex min-w-0 items-center gap-2">
405
+ {selectedOption.icon && (
406
+ <span className="shrink-0">{selectedOption.icon}</span>
407
+ )}
408
+ <span className="truncate">{selectedOption.label}</span>
409
+ </div>
410
+ ) : (
411
+ <span className="text-muted-foreground">{placeholder}</span>
412
+ )}
413
+ <ChevronDown className="h-3.5 w-3.5 rotate-180 text-muted-foreground transition-transform" />
414
+ </button>
415
+ )
236
416
  )}
237
417
 
238
418
  {/* Absolute dropdown */}
@@ -243,46 +423,10 @@ export function SearchSelect({
243
423
  animate={{ opacity: 1, y: 0 }}
244
424
  exit={{ opacity: 0, y: isUp ? 4 : -4 }}
245
425
  transition={{ duration: 0.12 }}
246
- className={`absolute left-0 right-0 z-50 rounded-md border border-input bg-card shadow-lg overflow-hidden ${isUp ? "bottom-full mb-1" : "mt-1"}`}
426
+ className={`absolute left-0 right-0 z-50 overflow-hidden rounded-md border border-input bg-card shadow-lg ${isUp ? "bottom-full mb-1" : "mt-1"}`}
247
427
  >
248
- <div className="p-1.5">
249
- <Input
250
- ref={inputRef}
251
- icon={Search}
252
- size="sm"
253
- value={query}
254
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
255
- setQuery(e.target.value);
256
- setActiveIndex(0);
257
- }}
258
- onKeyDown={handleKeyDown}
259
- placeholder={searchPlaceholder}
260
- className="border-0 shadow-none focus-visible:ring-0 focus-visible:border-transparent"
261
- />
262
- </div>
263
- {filtered.length > 0 && (
264
- <div className="border-t border-border max-h-[192px] overflow-y-auto">
265
- {filtered.map((opt, i) => (
266
- <button
267
- key={opt.value}
268
- type="button"
269
- data-active={i === activeIndex}
270
- onClick={() => select(opt.value)}
271
- onMouseEnter={() => setActiveIndex(i)}
272
- className={`flex w-full items-center px-3 py-2 text-left transition-colors ${
273
- i === activeIndex ? "bg-primary/10" : "hover:bg-muted"
274
- }`}
275
- >
276
- {optionRenderer(opt, i === activeIndex, opt.value === value)}
277
- </button>
278
- ))}
279
- </div>
280
- )}
281
- {filtered.length === 0 && query && (
282
- <div className="border-t border-border px-3 py-3 text-center text-xs text-muted-foreground">
283
- Brak wyników
284
- </div>
285
- )}
428
+ {renderSearchInput()}
429
+ {renderOptionList()}
286
430
  </motion.div>
287
431
  )}
288
432
  </AnimatePresence>
@@ -0,0 +1,60 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type { ReactNode } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ import { Box } from "../box/box";
5
+
6
+ /**
7
+ * Sidebar — vertical container for navigation/auxiliary content beside main
8
+ * page content. Typically registered into a layout slot like `sidebar-left`.
9
+ *
10
+ * The `sticky` variant pins the sidebar next to the toolbar and stretches it
11
+ * to fill the visible viewport height. It relies on the CSS variable
12
+ * `--arc-toolbar-height` set by the desktop `Layout` (measured from the
13
+ * actual rendered toolbar via ResizeObserver). Falls back to `5rem` if the
14
+ * variable isn't set.
15
+ */
16
+ export const sidebarVariants = cva("flex flex-col gap-3 p-4", {
17
+ variants: {
18
+ variant: {
19
+ default: "",
20
+ sticky:
21
+ "sticky top-[var(--arc-toolbar-height,5rem)] " +
22
+ "max-h-[calc(100vh-var(--arc-toolbar-height,5rem)-1rem)] " +
23
+ "overflow-y-auto",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ },
29
+ });
30
+
31
+ export interface SidebarProps extends VariantProps<typeof sidebarVariants> {
32
+ children?: ReactNode;
33
+ className?: string;
34
+ /** Title rendered at the top with uppercase muted styling. */
35
+ title?: ReactNode;
36
+ /** Footer rendered at the bottom with a top border. */
37
+ footer?: ReactNode;
38
+ }
39
+
40
+ export function Sidebar({
41
+ variant,
42
+ children,
43
+ className,
44
+ title,
45
+ footer,
46
+ }: SidebarProps) {
47
+ return (
48
+ <Box className={cn(sidebarVariants({ variant }), className)}>
49
+ {title && (
50
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
51
+ {title}
52
+ </h3>
53
+ )}
54
+ <div className="flex-1 min-h-0">{children}</div>
55
+ {footer && (
56
+ <div className="pt-2 border-t border-border">{footer}</div>
57
+ )}
58
+ </Box>
59
+ );
60
+ }
package/src/ds/types.ts CHANGED
@@ -50,6 +50,7 @@ export interface ButtonProps {
50
50
  | "secondary"
51
51
  | "ghost"
52
52
  | "outline"
53
+ | "outline-dashed"
53
54
  | "destructive"
54
55
  | "link";
55
56
  size?: "default" | "sm" | "xs" | "lg" | "icon" | "icon-sm" | "icon-xs";
package/src/index.ts CHANGED
@@ -54,7 +54,7 @@ export {
54
54
  } from "./ds/avatar/avatar";
55
55
  export { BentoCard } from "./ds/bento-card/bento-card";
56
56
  export type { BentoCardProps } from "./ds/bento-card/bento-card";
57
- export { BentoGrid, useBentoSpan } from "./ds/bento-grid/bento-grid";
57
+ export { BentoGrid } from "./ds/bento-grid/bento-grid";
58
58
  export type { BentoGridProps } from "./ds/bento-grid/bento-grid";
59
59
  export { CardModal, CardFormModal, ModalActions } from "./ds/card-modal/card-modal";
60
60
  export type { CardModalProps, CardFormModalProps, ModalActionsProps } from "./ds/card-modal/card-modal";
@@ -63,10 +63,14 @@ export type { TagListProps } from "./ds/tag-list/tag-list";
63
63
  export { SuggestionList } from "./ds/suggestion-list/suggestion-list";
64
64
  export type { SuggestionListProps, InitialCloudConfig } from "./ds/suggestion-list/suggestion-list";
65
65
  export { SearchSelect } from "./ds/search-select/search-select";
66
- export type { SearchSelectProps, SearchSelectOption } from "./ds/search-select/search-select";
66
+ export type { SearchSelectProps, SearchSelectOption, SearchSelectTriggerProps, SearchSelectMultiTriggerProps } from "./ds/search-select/search-select";
67
+ export { EditableText } from "./ds/editable-text/editable-text";
68
+ export type { EditableTextProps } from "./ds/editable-text/editable-text";
67
69
  export { Badge, badgeVariants } from "./ds/badge/badge";
68
70
  export { Box, boxVariants } from "./ds/box/box";
69
71
  export type { BoxProps } from "./ds/box/box";
72
+ export { Sidebar, sidebarVariants } from "./ds/sidebar/sidebar";
73
+ export type { SidebarProps } from "./ds/sidebar/sidebar";
70
74
  export {
71
75
  Button,
72
76
  buttonIconVariants,
@@ -111,6 +115,12 @@ export type { ChatToolLogProps } from "./ds/chat/chat-tool-log";
111
115
  export { ChatToolQuestion } from "./ds/chat/chat-tool-question";
112
116
  export type { ChatToolQuestionProps } from "./ds/chat/chat-tool-question";
113
117
  export { ChatInputProvider, useChatInput } from "./ds/chat/chat-input-provider";
118
+ export {
119
+ ChatLabelsProvider,
120
+ useChatLabels,
121
+ defaultChatLabels,
122
+ } from "./ds/chat/chat-labels";
123
+ export type { ChatLabels } from "./ds/chat/chat-labels";
114
124
  export type {
115
125
  ChatMessageData,
116
126
  ChatModel,