@trycompai/design-system 1.0.10 → 1.0.12
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/package.json
CHANGED
|
@@ -63,14 +63,24 @@ function AIChat({
|
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
65
|
<>
|
|
66
|
-
{/*
|
|
66
|
+
{/* Backdrop overlay */}
|
|
67
|
+
<div
|
|
68
|
+
data-slot="ai-chat-backdrop"
|
|
69
|
+
className={`fixed inset-0 z-20 bg-black/5 backdrop-blur-[2px] transition-opacity duration-300 ${
|
|
70
|
+
isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
71
|
+
}`}
|
|
72
|
+
onClick={handleClose}
|
|
73
|
+
/>
|
|
74
|
+
{/* Chat Panel - floating module */}
|
|
67
75
|
<div
|
|
68
76
|
data-slot="ai-chat-panel"
|
|
69
|
-
className={`fixed top-
|
|
70
|
-
isOpen ? 'translate-x-0' : 'translate-x-
|
|
77
|
+
className={`fixed top-16 right-3 bottom-3 z-30 w-full max-w-md flex flex-col bg-background/95 backdrop-blur-xl border border-border/50 rounded-2xl overflow-hidden transition-all duration-300 ease-out ring-1 ring-black/[0.03] dark:ring-white/[0.05] ${
|
|
78
|
+
isOpen ? 'translate-x-0 opacity-100 scale-100' : 'translate-x-2 opacity-0 scale-[0.98] pointer-events-none'
|
|
71
79
|
}`}
|
|
72
80
|
style={{
|
|
73
|
-
boxShadow: isOpen
|
|
81
|
+
boxShadow: isOpen
|
|
82
|
+
? '0 24px 48px -12px rgb(0 0 0 / 0.15), 0 12px 24px -8px rgb(0 0 0 / 0.1), 0 0 0 1px rgb(0 0 0 / 0.03)'
|
|
83
|
+
: 'none',
|
|
74
84
|
}}
|
|
75
85
|
>
|
|
76
86
|
{children || <AIChatDefaultContent onClose={handleClose} />}
|
|
@@ -120,7 +130,7 @@ function AIChatTrigger({ onClick, isOpen, shortcut = 'J' }: AIChatTriggerProps)
|
|
|
120
130
|
// Default content when no children provided
|
|
121
131
|
function AIChatDefaultContent({ onClose }: { onClose: () => void }) {
|
|
122
132
|
const [message, setMessage] = React.useState('');
|
|
123
|
-
const inputRef = React.useRef<
|
|
133
|
+
const inputRef = React.useRef<HTMLTextAreaElement>(null);
|
|
124
134
|
|
|
125
135
|
// Focus input when panel opens
|
|
126
136
|
React.useEffect(() => {
|
|
@@ -128,43 +138,62 @@ function AIChatDefaultContent({ onClose }: { onClose: () => void }) {
|
|
|
128
138
|
return () => clearTimeout(timer);
|
|
129
139
|
}, []);
|
|
130
140
|
|
|
141
|
+
// Auto-resize textarea
|
|
142
|
+
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
143
|
+
setMessage(e.target.value);
|
|
144
|
+
e.target.style.height = 'auto';
|
|
145
|
+
e.target.style.height = `${Math.min(e.target.scrollHeight, 120)}px`;
|
|
146
|
+
};
|
|
147
|
+
|
|
131
148
|
return (
|
|
132
149
|
<>
|
|
133
150
|
{/* Header */}
|
|
134
|
-
<div className="flex items-center justify-between px-
|
|
151
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-border/50 shrink-0 bg-muted/30">
|
|
135
152
|
<div className="flex items-center gap-3">
|
|
136
|
-
<span className="flex size-
|
|
153
|
+
<span className="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-primary/20 to-primary/5 text-primary ring-1 ring-primary/10">
|
|
137
154
|
<MagicWand className="size-5" />
|
|
138
155
|
</span>
|
|
139
156
|
<div>
|
|
140
157
|
<div className="font-semibold text-sm">AI Assistant</div>
|
|
141
|
-
<div className="text-xs text-muted-foreground">
|
|
158
|
+
<div className="text-xs text-muted-foreground">Powered by Claude</div>
|
|
142
159
|
</div>
|
|
143
160
|
</div>
|
|
144
161
|
<button
|
|
145
162
|
type="button"
|
|
146
163
|
onClick={onClose}
|
|
147
|
-
className="size-8 flex items-center justify-center rounded-
|
|
164
|
+
className="size-8 flex items-center justify-center rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
148
165
|
>
|
|
149
166
|
<Close className="size-4" />
|
|
150
167
|
</button>
|
|
151
168
|
</div>
|
|
152
169
|
|
|
153
170
|
{/* Messages Area */}
|
|
154
|
-
<div className="flex-1 overflow-auto p-
|
|
171
|
+
<div className="flex-1 overflow-auto p-5">
|
|
155
172
|
<div className="flex flex-col gap-4">
|
|
156
173
|
{/* AI Welcome Message */}
|
|
157
174
|
<div className="flex gap-3">
|
|
158
|
-
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/
|
|
175
|
+
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-primary/15 to-primary/5 text-primary">
|
|
159
176
|
<MagicWand className="size-4" />
|
|
160
177
|
</span>
|
|
161
|
-
<div className="flex-1 rounded-2xl rounded-tl-
|
|
162
|
-
<p className="mb-
|
|
163
|
-
<ul className="space-y-
|
|
164
|
-
<li
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
<div className="flex-1 rounded-2xl rounded-tl-lg bg-muted/50 ring-1 ring-border/50 px-4 py-3 text-sm">
|
|
179
|
+
<p className="mb-3 font-medium">Hi! I can help you with:</p>
|
|
180
|
+
<ul className="space-y-2 text-muted-foreground">
|
|
181
|
+
<li className="flex items-start gap-2">
|
|
182
|
+
<span className="size-1.5 rounded-full bg-primary/60 mt-1.5 shrink-0" />
|
|
183
|
+
<span>Understanding compliance requirements</span>
|
|
184
|
+
</li>
|
|
185
|
+
<li className="flex items-start gap-2">
|
|
186
|
+
<span className="size-1.5 rounded-full bg-primary/60 mt-1.5 shrink-0" />
|
|
187
|
+
<span>Reviewing and creating policies</span>
|
|
188
|
+
</li>
|
|
189
|
+
<li className="flex items-start gap-2">
|
|
190
|
+
<span className="size-1.5 rounded-full bg-primary/60 mt-1.5 shrink-0" />
|
|
191
|
+
<span>Analyzing evidence and controls</span>
|
|
192
|
+
</li>
|
|
193
|
+
<li className="flex items-start gap-2">
|
|
194
|
+
<span className="size-1.5 rounded-full bg-primary/60 mt-1.5 shrink-0" />
|
|
195
|
+
<span>Answering questions about SOC 2, ISO 27001, and more</span>
|
|
196
|
+
</li>
|
|
168
197
|
</ul>
|
|
169
198
|
</div>
|
|
170
199
|
</div>
|
|
@@ -172,31 +201,35 @@ function AIChatDefaultContent({ onClose }: { onClose: () => void }) {
|
|
|
172
201
|
</div>
|
|
173
202
|
|
|
174
203
|
{/* Input Area */}
|
|
175
|
-
<div className="p-4 border-t border-border shrink-0">
|
|
176
|
-
<div className="flex items-
|
|
177
|
-
<
|
|
204
|
+
<div className="p-4 border-t border-border/50 shrink-0 bg-muted/20">
|
|
205
|
+
<div className="flex items-end gap-2 rounded-xl bg-background ring-1 ring-border/50 px-4 py-3 focus-within:ring-2 focus-within:ring-primary/20 focus-within:border-primary/30 transition-all">
|
|
206
|
+
<textarea
|
|
178
207
|
ref={inputRef}
|
|
179
|
-
type="text"
|
|
180
208
|
value={message}
|
|
181
|
-
onChange={
|
|
182
|
-
placeholder="Ask
|
|
183
|
-
|
|
209
|
+
onChange={handleTextareaChange}
|
|
210
|
+
placeholder="Ask anything..."
|
|
211
|
+
rows={1}
|
|
212
|
+
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground resize-none min-h-[24px] max-h-[120px]"
|
|
184
213
|
onKeyDown={(e) => {
|
|
185
|
-
if (e.key === 'Enter' && message.trim()) {
|
|
214
|
+
if (e.key === 'Enter' && !e.shiftKey && message.trim()) {
|
|
215
|
+
e.preventDefault();
|
|
186
216
|
// Handle send
|
|
187
217
|
setMessage('');
|
|
218
|
+
if (inputRef.current) {
|
|
219
|
+
inputRef.current.style.height = 'auto';
|
|
220
|
+
}
|
|
188
221
|
}
|
|
189
222
|
}}
|
|
190
223
|
/>
|
|
191
224
|
<button
|
|
192
225
|
type="button"
|
|
193
226
|
disabled={!message.trim()}
|
|
194
|
-
className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-
|
|
227
|
+
className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-all disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-primary shadow-sm"
|
|
195
228
|
>
|
|
196
229
|
<Send className="size-4" />
|
|
197
230
|
</button>
|
|
198
231
|
</div>
|
|
199
|
-
<div className="mt-3 flex items-center justify-center gap-2 text-xs text-muted-foreground">
|
|
232
|
+
<div className="mt-3 flex items-center justify-center gap-2 text-xs text-muted-foreground/70">
|
|
200
233
|
<Keyboard className="size-3" />
|
|
201
234
|
<span>Press <Kbd>Esc</Kbd> to close</span>
|
|
202
235
|
</div>
|
|
@@ -156,7 +156,7 @@ function CardFooter({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>
|
|
|
156
156
|
return (
|
|
157
157
|
<div
|
|
158
158
|
data-slot="card-footer"
|
|
159
|
-
className="bg-muted rounded-b-md border-t p-4 group-data-[size=sm]/card:p-3 flex items-center"
|
|
159
|
+
className="bg-muted rounded-b-md border-t p-4 group-data-[size=sm]/card:p-3 flex items-center justify-between gap-4"
|
|
160
160
|
{...props}
|
|
161
161
|
/>
|
|
162
162
|
);
|
|
@@ -20,6 +20,7 @@ interface PageHeaderProps extends Omit<React.ComponentProps<'div'>, 'className'>
|
|
|
20
20
|
function PageHeader({ title, actions, breadcrumbs, backHref, backLabel = 'Back', tabs, children, ...props }: PageHeaderProps) {
|
|
21
21
|
const childArray = React.Children.toArray(children);
|
|
22
22
|
const extractedActionChildren: React.ReactNode[] = [];
|
|
23
|
+
const extractedDescriptionChildren: React.ReactNode[] = [];
|
|
23
24
|
|
|
24
25
|
childArray.forEach((child) => {
|
|
25
26
|
if (
|
|
@@ -30,6 +31,14 @@ function PageHeader({ title, actions, breadcrumbs, backHref, backLabel = 'Back',
|
|
|
30
31
|
) {
|
|
31
32
|
extractedActionChildren.push((child.props as { children?: React.ReactNode }).children);
|
|
32
33
|
}
|
|
34
|
+
if (
|
|
35
|
+
React.isValidElement(child) &&
|
|
36
|
+
(child.type === PageHeaderDescription ||
|
|
37
|
+
(typeof child.type === 'function' &&
|
|
38
|
+
(child.type as unknown as { __pageHeaderSlot?: string }).__pageHeaderSlot === 'description'))
|
|
39
|
+
) {
|
|
40
|
+
extractedDescriptionChildren.push(child);
|
|
41
|
+
}
|
|
33
42
|
});
|
|
34
43
|
|
|
35
44
|
const resolvedActions =
|
|
@@ -69,6 +78,9 @@ function PageHeader({ title, actions, breadcrumbs, backHref, backLabel = 'Back',
|
|
|
69
78
|
)}
|
|
70
79
|
</div>
|
|
71
80
|
|
|
81
|
+
{/* Description section */}
|
|
82
|
+
{extractedDescriptionChildren.length > 0 && extractedDescriptionChildren}
|
|
83
|
+
|
|
72
84
|
{/* Tabs section */}
|
|
73
85
|
{tabs && (
|
|
74
86
|
<div className="mt-2 -mb-px">
|
|
@@ -85,7 +97,14 @@ function PageHeaderActions({ ...props }: Omit<React.ComponentProps<'div'>, 'clas
|
|
|
85
97
|
);
|
|
86
98
|
}
|
|
87
99
|
|
|
100
|
+
function PageHeaderDescription({ ...props }: Omit<React.ComponentProps<'p'>, 'className'>) {
|
|
101
|
+
return (
|
|
102
|
+
<p data-slot="page-header-description" className="text-sm text-muted-foreground" {...props} />
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
// Mark compound slots so PageHeader can detect them even if module instances differ.
|
|
89
107
|
(PageHeaderActions as unknown as { __pageHeaderSlot?: string }).__pageHeaderSlot = 'actions';
|
|
108
|
+
(PageHeaderDescription as unknown as { __pageHeaderSlot?: string }).__pageHeaderSlot = 'description';
|
|
90
109
|
|
|
91
|
-
export { PageHeader, PageHeaderActions };
|
|
110
|
+
export { PageHeader, PageHeaderActions, PageHeaderDescription };
|
|
@@ -12,14 +12,14 @@ const pageLayoutVariants = cva('min-h-full bg-background text-foreground', {
|
|
|
12
12
|
},
|
|
13
13
|
padding: {
|
|
14
14
|
none: '',
|
|
15
|
-
sm: 'px-
|
|
16
|
-
default: 'px-
|
|
17
|
-
lg: 'px-
|
|
15
|
+
sm: 'px-1.5 sm:px-2',
|
|
16
|
+
default: 'px-1.5 sm:px-2 md:px-3 lg:px-4',
|
|
17
|
+
lg: 'px-2 sm:px-3 md:px-4 lg:px-6',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultVariants: {
|
|
21
21
|
variant: 'default',
|
|
22
|
-
padding: '
|
|
22
|
+
padding: 'none',
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -34,7 +34,7 @@ const containerVariants = cva('mx-auto w-full', {
|
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
defaultVariants: {
|
|
37
|
-
maxWidth: '
|
|
37
|
+
maxWidth: '2xl',
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -60,7 +60,7 @@ function PageLayout({
|
|
|
60
60
|
...props
|
|
61
61
|
}: PageLayoutProps) {
|
|
62
62
|
// For center variant, default to smaller max-width (sm) for auth-style pages
|
|
63
|
-
const resolvedMaxWidth = maxWidth ?? (variant === 'center' ? 'sm' : '
|
|
63
|
+
const resolvedMaxWidth = maxWidth ?? (variant === 'center' ? 'sm' : 'xl');
|
|
64
64
|
|
|
65
65
|
const content = (
|
|
66
66
|
<Stack gap={gap}>
|