@epicbagel/saas-kit 1.25.1 → 1.27.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/README.md CHANGED
@@ -543,6 +543,56 @@ Inside a `MessageScrollerProvider`, use `MessageScrollerNavigator` from
543
543
  `primitives/message-scroller` with the same props. It uses the thread's scroll
544
544
  controller so incoming messages do not undo a jump into a long list.
545
545
 
546
+ ### Moving through a conversation (1.26.0)
547
+
548
+ `Chat` and `ChatThread` take `navigator`: an array of `ChatNavigatorItem`.
549
+ Each item has a `messageId` from the thread and a `title`, plus an optional
550
+ `excerpt` and `kind` ("Plan", "Summary").
551
+
552
+ `ChatTurnNavigator` draws them as a rail of short ticks in the thread's gutter.
553
+ - **Which tick is marked:** the one for the place in view, read from the
554
+ scroller's own visibility tracking (`activeNavigatorItem`).
555
+ - **Preview:** hovering or focusing a tick shows its kind, title and excerpt.
556
+ - **Jump:** clicking scrolls through the thread's anchoring, as
557
+ `MessageScrollerNavigator` does.
558
+ - **Keyboard:** arrow keys, Home and End move between ticks, and the rail is
559
+ one tab stop.
560
+ - **Placement:** the start gutter by default, or `navigatorSide="end"`, under
561
+ `navigatorLabel` (default "Conversation"). The rail only shows where the
562
+ gutter beside the reading column has room, so it stays out of a narrow
563
+ thread or a phone.
564
+
565
+ `ChatMessage.author` (`{ id, name, avatarUrl?, detail? }`) names who wrote a
566
+ turn. The thread draws the avatar and name above the first of each run of
567
+ turns by the same person on the same side, and again after a time marker, so
568
+ a conversation with several people in it says who said what.
569
+
570
+ ### Next steps in the empty state (1.27.0)
571
+
572
+ `Chat` takes `emptyActions`, an array of `ChatEmptyAction`: what a returning
573
+ user can pick up, shown under the empty-state composer and before any
574
+ `examplePrompts`.
575
+
576
+ ```tsx
577
+ <Chat
578
+ messages={[]}
579
+ onSend={send}
580
+ emptyActions={[
581
+ { id: "review", icon: Users, label: "Review 3 new people", detail: "Head of Sales · Northwind", onSelect: openReview },
582
+ { id: "answer", icon: MessageCircleQuestion, label: "London", detail: "Where is the role based? · VP Finance", onSelect: answer },
583
+ ]}
584
+ />
585
+ ```
586
+
587
+ - **A row** is a button: the icon, the `label` and the muted `detail`, on one
588
+ line where there is room.
589
+ - **A pick** calls `onSelect` and sends nothing. Return a promise to show the
590
+ row's spinner and disable every row until it settles; a rejection
591
+ re-enables them and goes to `onSendError` (else `reportError`).
592
+ - **The heading** is `emptyActionsLabel`, default "Pick up where you left
593
+ off". `""` hides it and keeps the default as the list's accessible name.
594
+ - It hides while `pending` and once the conversation has started.
595
+
546
596
  ## Notes
547
597
 
548
598
  `primitives/notes` provides a reusable `Notes` area and `NoteComposer` for any
@@ -0,0 +1,198 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
+
3
+
4
+ var _chunkNQK5ZWC7cjs = require('./chunk-NQK5ZWC7.cjs');
5
+
6
+
7
+ var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
8
+
9
+ // src/primitives/item.tsx
10
+ var _classvarianceauthority = require('class-variance-authority');
11
+ var _radixui = require('radix-ui');
12
+ var _jsxruntime = require('react/jsx-runtime');
13
+ function ItemGroup({ className, ...props }) {
14
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
15
+ "div",
16
+ {
17
+ role: "list",
18
+ "data-slot": "item-group",
19
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
20
+ "group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
21
+ className
22
+ ),
23
+ ...props
24
+ }
25
+ );
26
+ }
27
+ function ItemSeparator({
28
+ className,
29
+ ...props
30
+ }) {
31
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
32
+ _chunkNQK5ZWC7cjs.Separator,
33
+ {
34
+ "data-slot": "item-separator",
35
+ orientation: "horizontal",
36
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "my-2", className),
37
+ ...props
38
+ }
39
+ );
40
+ }
41
+ var itemVariants = _classvarianceauthority.cva.call(void 0,
42
+ "group/item flex w-full flex-wrap items-center rounded-md border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted",
43
+ {
44
+ variants: {
45
+ variant: {
46
+ default: "border-transparent",
47
+ outline: "border-border",
48
+ muted: "border-transparent bg-muted/50"
49
+ },
50
+ size: {
51
+ default: "gap-3.5 px-4 py-3.5",
52
+ sm: "gap-2.5 px-3 py-2.5",
53
+ xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0"
54
+ }
55
+ },
56
+ defaultVariants: {
57
+ variant: "default",
58
+ size: "default"
59
+ }
60
+ }
61
+ );
62
+ function Item({
63
+ className,
64
+ variant = "default",
65
+ size = "default",
66
+ asChild = false,
67
+ ...props
68
+ }) {
69
+ const Comp = asChild ? _radixui.Slot.Root : "div";
70
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
71
+ Comp,
72
+ {
73
+ "data-slot": "item",
74
+ "data-variant": variant,
75
+ "data-size": size,
76
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, itemVariants({ variant, size, className })),
77
+ ...props
78
+ }
79
+ );
80
+ }
81
+ var itemMediaVariants = _classvarianceauthority.cva.call(void 0,
82
+ "flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
83
+ {
84
+ variants: {
85
+ variant: {
86
+ default: "bg-transparent",
87
+ icon: "[&_svg:not([class*='size-'])]:size-4",
88
+ image: "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover"
89
+ }
90
+ },
91
+ defaultVariants: {
92
+ variant: "default"
93
+ }
94
+ }
95
+ );
96
+ function ItemMedia({
97
+ className,
98
+ variant = "default",
99
+ ...props
100
+ }) {
101
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
102
+ "div",
103
+ {
104
+ "data-slot": "item-media",
105
+ "data-variant": variant,
106
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, itemMediaVariants({ variant, className })),
107
+ ...props
108
+ }
109
+ );
110
+ }
111
+ function ItemContent({ className, ...props }) {
112
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
113
+ "div",
114
+ {
115
+ "data-slot": "item-content",
116
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
117
+ "flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
118
+ className
119
+ ),
120
+ ...props
121
+ }
122
+ );
123
+ }
124
+ function ItemTitle({ className, ...props }) {
125
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
126
+ "div",
127
+ {
128
+ "data-slot": "item-title",
129
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
130
+ "line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
131
+ className
132
+ ),
133
+ ...props
134
+ }
135
+ );
136
+ }
137
+ function ItemDescription({ className, ...props }) {
138
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
139
+ "p",
140
+ {
141
+ "data-slot": "item-description",
142
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
143
+ "line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
144
+ className
145
+ ),
146
+ ...props
147
+ }
148
+ );
149
+ }
150
+ function ItemActions({ className, ...props }) {
151
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
152
+ "div",
153
+ {
154
+ "data-slot": "item-actions",
155
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex items-center gap-2", className),
156
+ ...props
157
+ }
158
+ );
159
+ }
160
+ function ItemHeader({ className, ...props }) {
161
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
162
+ "div",
163
+ {
164
+ "data-slot": "item-header",
165
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
166
+ "flex basis-full items-center justify-between gap-2",
167
+ className
168
+ ),
169
+ ...props
170
+ }
171
+ );
172
+ }
173
+ function ItemFooter({ className, ...props }) {
174
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
175
+ "div",
176
+ {
177
+ "data-slot": "item-footer",
178
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
179
+ "flex basis-full items-center justify-between gap-2",
180
+ className
181
+ ),
182
+ ...props
183
+ }
184
+ );
185
+ }
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+ exports.ItemGroup = ItemGroup; exports.ItemSeparator = ItemSeparator; exports.Item = Item; exports.ItemMedia = ItemMedia; exports.ItemContent = ItemContent; exports.ItemTitle = ItemTitle; exports.ItemDescription = ItemDescription; exports.ItemActions = ItemActions; exports.ItemHeader = ItemHeader; exports.ItemFooter = ItemFooter;
@@ -0,0 +1,158 @@
1
+ "use client";
2
+ import {
3
+ useMessageScroller,
4
+ useMessageScrollerVisibility
5
+ } from "./chunk-OUZSIA5G.js";
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipProvider,
10
+ TooltipTrigger
11
+ } from "./chunk-ZRLUUZHK.js";
12
+ import {
13
+ cn
14
+ } from "./chunk-G6SRSWXB.js";
15
+
16
+ // src/primitives/chat-turn-navigator.tsx
17
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
18
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
19
+ import { jsx, jsxs } from "react/jsx-runtime";
20
+ var RAIL_WIDTH = 40;
21
+ var RAIL_GAP = 16;
22
+ function activeNavigatorItem(items, order, visibleMessageIds) {
23
+ if (items.length === 0) return null;
24
+ const position = new Map(order.map((id, index) => [id, index]));
25
+ const firstVisible = visibleMessageIds.map((id) => position.get(id)).filter((index) => index !== void 0).sort((a, b) => a - b)[0];
26
+ if (firstVisible === void 0) return null;
27
+ let active = null;
28
+ for (const item of items) {
29
+ const index = position.get(item.messageId);
30
+ if (index !== void 0 && index <= firstVisible) active = item.messageId;
31
+ }
32
+ return active ?? items[0].messageId;
33
+ }
34
+ function ChatTurnNavigator({
35
+ items,
36
+ order,
37
+ label = "Conversation",
38
+ side = "start",
39
+ columnWidth = 768
40
+ }) {
41
+ const { scrollToMessage } = useMessageScroller();
42
+ const { visibleMessageIds } = useMessageScrollerVisibility();
43
+ const navRef = useRef(null);
44
+ const listRef = useRef(null);
45
+ const [fits, setFits] = useState(false);
46
+ const [focusIndex, setFocusIndex] = useState(0);
47
+ const active = useMemo(
48
+ () => activeNavigatorItem(items, order, visibleMessageIds),
49
+ [items, order, visibleMessageIds]
50
+ );
51
+ useLayoutEffect(() => {
52
+ const root = navRef.current?.parentElement;
53
+ if (!root) return;
54
+ const measure = () => setFits((root.clientWidth - columnWidth) / 2 >= RAIL_WIDTH + RAIL_GAP * 2);
55
+ measure();
56
+ if (typeof ResizeObserver === "undefined") return;
57
+ const observer = new ResizeObserver(measure);
58
+ observer.observe(root);
59
+ return () => observer.disconnect();
60
+ }, [columnWidth]);
61
+ useEffect(() => {
62
+ const list = listRef.current;
63
+ const index = items.findIndex((item) => item.messageId === active);
64
+ const tick = list?.children[index];
65
+ if (!list || !tick) return;
66
+ if (tick.offsetTop < list.scrollTop) list.scrollTop = tick.offsetTop;
67
+ else if (tick.offsetTop + tick.offsetHeight > list.scrollTop + list.clientHeight) {
68
+ list.scrollTop = tick.offsetTop + tick.offsetHeight - list.clientHeight;
69
+ }
70
+ if (index >= 0) setFocusIndex(index);
71
+ }, [active, items]);
72
+ if (items.length === 0) return null;
73
+ function jump(item) {
74
+ const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
75
+ scrollToMessage(item.messageId, { align: "start", behavior: reduced ? "instant" : "smooth", scrollMargin: 16 });
76
+ }
77
+ function onKeyDown(event, index) {
78
+ const next = event.key === "ArrowDown" ? index + 1 : event.key === "ArrowUp" ? index - 1 : event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : null;
79
+ if (next === null) return;
80
+ event.preventDefault();
81
+ const target = Math.max(0, Math.min(items.length - 1, next));
82
+ setFocusIndex(target);
83
+ listRef.current?.children[target]?.querySelector("button")?.focus({ preventScroll: true });
84
+ }
85
+ return /* @__PURE__ */ jsx(
86
+ "nav",
87
+ {
88
+ ref: navRef,
89
+ "aria-label": label,
90
+ hidden: !fits,
91
+ "data-slot": "chat-turn-navigator",
92
+ className: cn(
93
+ "absolute top-1/2 z-10 -translate-y-1/2",
94
+ side === "start" ? "left-4" : "right-5"
95
+ ),
96
+ children: /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 150, skipDelayDuration: 300, children: /* @__PURE__ */ jsx(
97
+ "ol",
98
+ {
99
+ ref: listRef,
100
+ className: "flex max-h-[60vh] flex-col overflow-y-auto py-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
101
+ children: items.map((item, index) => {
102
+ const current = item.messageId === active;
103
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
104
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
105
+ "button",
106
+ {
107
+ type: "button",
108
+ "aria-label": item.kind ? `${item.kind}: ${item.title}` : item.title,
109
+ "aria-current": current ? "location" : void 0,
110
+ tabIndex: index === focusIndex ? 0 : -1,
111
+ onClick: () => jump(item),
112
+ onKeyDown: (event) => onKeyDown(event, index),
113
+ className: cn(
114
+ "group flex h-3.5 w-10 items-center rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
115
+ side === "start" ? "justify-start ps-1" : "justify-end pe-1"
116
+ ),
117
+ children: /* @__PURE__ */ jsx(
118
+ "span",
119
+ {
120
+ "aria-hidden": true,
121
+ className: cn(
122
+ "h-0.5 rounded-full transition-[width,background-color] duration-150",
123
+ current ? "w-6 bg-foreground" : "w-3.5 bg-muted-foreground/45 group-hover:w-5 group-hover:bg-muted-foreground group-focus-visible:w-5"
124
+ )
125
+ }
126
+ )
127
+ }
128
+ ) }),
129
+ /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
130
+ TooltipContent,
131
+ {
132
+ side: side === "start" ? "right" : "left",
133
+ sideOffset: 10,
134
+ className: "flex max-w-80 flex-col gap-1 rounded-xl px-3.5 py-2.5",
135
+ children: [
136
+ /* @__PURE__ */ jsxs("span", { className: "truncate font-medium", children: [
137
+ item.kind ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
138
+ item.kind,
139
+ " \xB7 "
140
+ ] }) : null,
141
+ item.title
142
+ ] }),
143
+ item.excerpt ? /* @__PURE__ */ jsx("span", { className: "line-clamp-3 text-xs leading-5 text-muted-foreground", children: item.excerpt }) : null
144
+ ]
145
+ }
146
+ ) })
147
+ ] }) }, item.messageId);
148
+ })
149
+ }
150
+ ) })
151
+ }
152
+ );
153
+ }
154
+
155
+ export {
156
+ activeNavigatorItem,
157
+ ChatTurnNavigator
158
+ };
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import {
3
+ Avatar,
4
+ AvatarFallback,
5
+ AvatarImage
6
+ } from "./chunk-3OWGL4Q2.js";
7
+ import {
8
+ cn
9
+ } from "./chunk-G6SRSWXB.js";
10
+
11
+ // src/primitives/person-avatar.tsx
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function initials(name) {
14
+ const words = name.trim().split(/\s+/).filter(Boolean);
15
+ const first = words[0];
16
+ if (!first) return "?";
17
+ const last = words[words.length - 1];
18
+ if (words.length === 1 || !last) return first.slice(0, 2).toUpperCase();
19
+ return (first.charAt(0) + last.charAt(0)).toUpperCase();
20
+ }
21
+ function PersonAvatar({
22
+ name,
23
+ photoUrl,
24
+ size,
25
+ className,
26
+ fallbackClassName,
27
+ fallbackSize = "default"
28
+ }) {
29
+ return /* @__PURE__ */ jsxs(
30
+ Avatar,
31
+ {
32
+ className: cn("shrink-0 overflow-hidden rounded-full bg-muted", className),
33
+ style: size != null ? { width: size, height: size } : void 0,
34
+ children: [
35
+ photoUrl ? /* @__PURE__ */ jsx(
36
+ AvatarImage,
37
+ {
38
+ src: photoUrl,
39
+ alt: name,
40
+ className: "h-full w-full object-cover"
41
+ }
42
+ ) : null,
43
+ /* @__PURE__ */ jsx(
44
+ AvatarFallback,
45
+ {
46
+ className: cn("h-full w-full font-medium", fallbackSize === "default" ? "text-xs" : "text-xl", fallbackClassName),
47
+ children: initials(name)
48
+ }
49
+ )
50
+ ]
51
+ }
52
+ );
53
+ }
54
+
55
+ export {
56
+ initials,
57
+ PersonAvatar
58
+ };
@@ -0,0 +1,198 @@
1
+ "use client";
2
+ import {
3
+ Separator
4
+ } from "./chunk-HYTILYE5.js";
5
+ import {
6
+ cn
7
+ } from "./chunk-G6SRSWXB.js";
8
+
9
+ // src/primitives/item.tsx
10
+ import { cva } from "class-variance-authority";
11
+ import { Slot } from "radix-ui";
12
+ import { jsx } from "react/jsx-runtime";
13
+ function ItemGroup({ className, ...props }) {
14
+ return /* @__PURE__ */ jsx(
15
+ "div",
16
+ {
17
+ role: "list",
18
+ "data-slot": "item-group",
19
+ className: cn(
20
+ "group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
21
+ className
22
+ ),
23
+ ...props
24
+ }
25
+ );
26
+ }
27
+ function ItemSeparator({
28
+ className,
29
+ ...props
30
+ }) {
31
+ return /* @__PURE__ */ jsx(
32
+ Separator,
33
+ {
34
+ "data-slot": "item-separator",
35
+ orientation: "horizontal",
36
+ className: cn("my-2", className),
37
+ ...props
38
+ }
39
+ );
40
+ }
41
+ var itemVariants = cva(
42
+ "group/item flex w-full flex-wrap items-center rounded-md border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted",
43
+ {
44
+ variants: {
45
+ variant: {
46
+ default: "border-transparent",
47
+ outline: "border-border",
48
+ muted: "border-transparent bg-muted/50"
49
+ },
50
+ size: {
51
+ default: "gap-3.5 px-4 py-3.5",
52
+ sm: "gap-2.5 px-3 py-2.5",
53
+ xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0"
54
+ }
55
+ },
56
+ defaultVariants: {
57
+ variant: "default",
58
+ size: "default"
59
+ }
60
+ }
61
+ );
62
+ function Item({
63
+ className,
64
+ variant = "default",
65
+ size = "default",
66
+ asChild = false,
67
+ ...props
68
+ }) {
69
+ const Comp = asChild ? Slot.Root : "div";
70
+ return /* @__PURE__ */ jsx(
71
+ Comp,
72
+ {
73
+ "data-slot": "item",
74
+ "data-variant": variant,
75
+ "data-size": size,
76
+ className: cn(itemVariants({ variant, size, className })),
77
+ ...props
78
+ }
79
+ );
80
+ }
81
+ var itemMediaVariants = cva(
82
+ "flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
83
+ {
84
+ variants: {
85
+ variant: {
86
+ default: "bg-transparent",
87
+ icon: "[&_svg:not([class*='size-'])]:size-4",
88
+ image: "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover"
89
+ }
90
+ },
91
+ defaultVariants: {
92
+ variant: "default"
93
+ }
94
+ }
95
+ );
96
+ function ItemMedia({
97
+ className,
98
+ variant = "default",
99
+ ...props
100
+ }) {
101
+ return /* @__PURE__ */ jsx(
102
+ "div",
103
+ {
104
+ "data-slot": "item-media",
105
+ "data-variant": variant,
106
+ className: cn(itemMediaVariants({ variant, className })),
107
+ ...props
108
+ }
109
+ );
110
+ }
111
+ function ItemContent({ className, ...props }) {
112
+ return /* @__PURE__ */ jsx(
113
+ "div",
114
+ {
115
+ "data-slot": "item-content",
116
+ className: cn(
117
+ "flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
118
+ className
119
+ ),
120
+ ...props
121
+ }
122
+ );
123
+ }
124
+ function ItemTitle({ className, ...props }) {
125
+ return /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ "data-slot": "item-title",
129
+ className: cn(
130
+ "line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
131
+ className
132
+ ),
133
+ ...props
134
+ }
135
+ );
136
+ }
137
+ function ItemDescription({ className, ...props }) {
138
+ return /* @__PURE__ */ jsx(
139
+ "p",
140
+ {
141
+ "data-slot": "item-description",
142
+ className: cn(
143
+ "line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
144
+ className
145
+ ),
146
+ ...props
147
+ }
148
+ );
149
+ }
150
+ function ItemActions({ className, ...props }) {
151
+ return /* @__PURE__ */ jsx(
152
+ "div",
153
+ {
154
+ "data-slot": "item-actions",
155
+ className: cn("flex items-center gap-2", className),
156
+ ...props
157
+ }
158
+ );
159
+ }
160
+ function ItemHeader({ className, ...props }) {
161
+ return /* @__PURE__ */ jsx(
162
+ "div",
163
+ {
164
+ "data-slot": "item-header",
165
+ className: cn(
166
+ "flex basis-full items-center justify-between gap-2",
167
+ className
168
+ ),
169
+ ...props
170
+ }
171
+ );
172
+ }
173
+ function ItemFooter({ className, ...props }) {
174
+ return /* @__PURE__ */ jsx(
175
+ "div",
176
+ {
177
+ "data-slot": "item-footer",
178
+ className: cn(
179
+ "flex basis-full items-center justify-between gap-2",
180
+ className
181
+ ),
182
+ ...props
183
+ }
184
+ );
185
+ }
186
+
187
+ export {
188
+ ItemGroup,
189
+ ItemSeparator,
190
+ Item,
191
+ ItemMedia,
192
+ ItemContent,
193
+ ItemTitle,
194
+ ItemDescription,
195
+ ItemActions,
196
+ ItemHeader,
197
+ ItemFooter
198
+ };