astryx-dracula 0.4.0 → 0.5.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/package.json +6 -2
- package/shared/auth-chrome.tsx +156 -6
- package/shared/chaptered-doc.tsx +3 -4
- package/shared/chart-hues.ts +0 -5
- package/shared/chart-legend.tsx +2 -3
- package/shared/data-bar.tsx +5 -5
- package/shared/format-money.ts +3 -0
- package/shared/metric-card.tsx +127 -0
- package/shared/scene-hues.ts +0 -8
- package/shared/scene-tile.tsx +3 -11
- package/shared/settings-data.ts +28 -0
- package/shared/settings-rows.tsx +1 -6
- package/shared/stars.tsx +28 -0
- package/templates/ai-chat-landing.tsx +88 -58
- package/templates/ai-chat.tsx +65 -78
- package/templates/centered-hero.tsx +2 -2
- package/templates/dashboard-portfolio.tsx +77 -155
- package/templates/dashboard.tsx +17 -134
- package/templates/detail-page.tsx +14 -18
- package/templates/documentation-design.tsx +66 -519
- package/templates/documentation-technical.tsx +2 -22
- package/templates/editor.tsx +168 -185
- package/templates/file-explorer.tsx +10 -24
- package/templates/ide.tsx +12 -18
- package/templates/incident-console.tsx +48 -46
- package/templates/kanban-board.tsx +36 -85
- package/templates/library.tsx +42 -66
- package/templates/login-card.tsx +33 -126
- package/templates/login-split.tsx +91 -123
- package/templates/login-sso.tsx +12 -33
- package/templates/login.tsx +21 -121
- package/templates/messaging-shell.tsx +162 -168
- package/templates/payment-form.tsx +24 -26
- package/templates/product-detail.tsx +7 -40
- package/templates/product-tour.tsx +38 -60
- package/templates/settings-dialog.tsx +10 -32
- package/templates/settings-sidebar.tsx +44 -77
- package/templates/settings.tsx +208 -121
- package/templates/shell-nav.tsx +20 -75
- package/templates/shell-side-nav.tsx +16 -90
- package/templates/shell-top-nav.tsx +20 -53
- package/templates/side-gallery.tsx +19 -19
- package/templates/table-grouped.tsx +17 -164
- package/templates/table-page-chart.tsx +12 -11
- package/templates/table-page-heatmap-status.tsx +0 -17
- package/templates/table-page-shoe-store-heatmap.tsx +12 -12
- package/templates/table-page.tsx +4 -31
- package/templates/table.tsx +7 -10
- package/templates/tech-report.tsx +58 -72
- package/templates/theme-showcase.tsx +124 -415
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ChatDictationButton,
|
|
14
14
|
useChatDictation,
|
|
15
15
|
type ChatComposerInputHandle,
|
|
16
|
+
type ChatComposerToken,
|
|
16
17
|
type ChatComposerTrigger,
|
|
17
18
|
} from '@astryxdesign/core/Chat';
|
|
18
19
|
import {
|
|
@@ -207,37 +208,54 @@ const COMMAND_ITEMS: SearchableItem<{description: string}>[] = [
|
|
|
207
208
|
},
|
|
208
209
|
];
|
|
209
210
|
|
|
210
|
-
|
|
211
|
+
// One factory for both composer triggers. Render (TypeaheadItem + auxiliary
|
|
212
|
+
// description) and select (token value/label/variant) differ per trigger;
|
|
213
|
+
// the trigger shape doesn't.
|
|
214
|
+
function makeTrigger<TAux>({
|
|
215
|
+
character,
|
|
216
|
+
items,
|
|
217
|
+
getDescription,
|
|
218
|
+
toToken,
|
|
219
|
+
}: {
|
|
220
|
+
character: string;
|
|
221
|
+
items: SearchableItem<TAux>[];
|
|
222
|
+
getDescription: (aux: TAux | undefined) => string | undefined;
|
|
223
|
+
toToken: (item: SearchableItem<TAux>) => string | ChatComposerToken;
|
|
224
|
+
}): ChatComposerTrigger {
|
|
225
|
+
return {
|
|
226
|
+
character,
|
|
227
|
+
searchSource: createStaticSource(items),
|
|
228
|
+
renderItem: item => (
|
|
229
|
+
<TypeaheadItem
|
|
230
|
+
item={item}
|
|
231
|
+
description={getDescription(item.auxiliaryData as TAux | undefined)}
|
|
232
|
+
/>
|
|
233
|
+
),
|
|
234
|
+
onSelect: item => toToken(item as SearchableItem<TAux>),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const mentionTrigger = makeTrigger({
|
|
211
239
|
character: '@',
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
item={item}
|
|
216
|
-
description={(item.auxiliaryData as {role: string})?.role}
|
|
217
|
-
/>
|
|
218
|
-
),
|
|
219
|
-
onSelect: item => ({
|
|
240
|
+
items: MENTION_ITEMS,
|
|
241
|
+
getDescription: aux => aux?.role,
|
|
242
|
+
toToken: item => ({
|
|
220
243
|
value: `@${item.id}`,
|
|
221
244
|
label: item.label,
|
|
222
245
|
variant: 'cyan',
|
|
223
246
|
}),
|
|
224
|
-
};
|
|
247
|
+
});
|
|
225
248
|
|
|
226
|
-
const commandTrigger
|
|
249
|
+
const commandTrigger = makeTrigger({
|
|
227
250
|
character: '/',
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
item={item}
|
|
232
|
-
description={(item.auxiliaryData as {description: string})?.description}
|
|
233
|
-
/>
|
|
234
|
-
),
|
|
235
|
-
onSelect: item => ({
|
|
251
|
+
items: COMMAND_ITEMS,
|
|
252
|
+
getDescription: aux => aux?.description,
|
|
253
|
+
toToken: item => ({
|
|
236
254
|
value: `/${item.label}`,
|
|
237
255
|
label: `/${item.label}`,
|
|
238
256
|
variant: 'yellow',
|
|
239
257
|
}),
|
|
240
|
-
};
|
|
258
|
+
});
|
|
241
259
|
|
|
242
260
|
const composerTriggers = [mentionTrigger, commandTrigger];
|
|
243
261
|
|
|
@@ -254,7 +272,7 @@ const INITIAL_ATTACHMENTS: Attachment[] = [
|
|
|
254
272
|
'api_spec.yaml',
|
|
255
273
|
'contrast_audit.csv',
|
|
256
274
|
'dracula_spec.pdf',
|
|
257
|
-
].map(
|
|
275
|
+
].map(name => ({id: crypto.randomUUID(), name}));
|
|
258
276
|
|
|
259
277
|
// The composer's imperative insert methods mutate the DOM without emitting a
|
|
260
278
|
// change, so dispatch an input event to sync its value and clear the placeholder.
|
|
@@ -262,6 +280,35 @@ const syncComposerValue = () => {
|
|
|
262
280
|
document.activeElement?.dispatchEvent(new Event('input', {bubbles: true}));
|
|
263
281
|
};
|
|
264
282
|
|
|
283
|
+
// One inserter for every composer write. `insertText` replaces the selection
|
|
284
|
+
// (suggestion prompts); `insertToken` inserts a chip at the cursor without
|
|
285
|
+
// touching the selection (mentions, mode tokens).
|
|
286
|
+
function insertIntoComposer(
|
|
287
|
+
input: ChatComposerInputHandle | null,
|
|
288
|
+
write: {text: string} | {token: ChatComposerToken},
|
|
289
|
+
collapseToEnd = false,
|
|
290
|
+
) {
|
|
291
|
+
if (!input) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
input.focus();
|
|
295
|
+
if (document.activeElement) {
|
|
296
|
+
if (collapseToEnd) {
|
|
297
|
+
const sel = window.getSelection();
|
|
298
|
+
sel?.selectAllChildren(document.activeElement);
|
|
299
|
+
sel?.collapseToEnd();
|
|
300
|
+
} else {
|
|
301
|
+
window.getSelection()?.selectAllChildren(document.activeElement);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if ('text' in write) {
|
|
305
|
+
input.insertText(write.text);
|
|
306
|
+
} else {
|
|
307
|
+
input.insertToken(write.token);
|
|
308
|
+
}
|
|
309
|
+
syncComposerValue();
|
|
310
|
+
}
|
|
311
|
+
|
|
265
312
|
// Main component
|
|
266
313
|
|
|
267
314
|
export default function AiChatLanding() {
|
|
@@ -273,53 +320,36 @@ export default function AiChatLanding() {
|
|
|
273
320
|
const [isModeMenuOpen, setIsModeMenuOpen] = useState(false);
|
|
274
321
|
const composerInputRef = useRef<ChatComposerInputHandle>(null);
|
|
275
322
|
const shouldFocusComposerRef = useRef(false);
|
|
276
|
-
//
|
|
277
|
-
const nextAttachmentId = useRef(INITIAL_ATTACHMENTS.length);
|
|
323
|
+
// Dictation mic stays: the composer still renders ChatDictationButton below.
|
|
278
324
|
const dictation = useChatDictation({inputRef: composerInputRef});
|
|
279
325
|
|
|
280
326
|
const activeMode = MODE_OPTIONS.find(m => m.key === mode) ?? MODE_OPTIONS[0];
|
|
281
327
|
const suggestions = category ? CATEGORY_SUGGESTIONS[category] : null;
|
|
282
328
|
|
|
283
329
|
const applySuggestion = (prompt: string) => {
|
|
284
|
-
|
|
285
|
-
if (!input) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
input.focus();
|
|
289
|
-
if (document.activeElement) {
|
|
290
|
-
window.getSelection()?.selectAllChildren(document.activeElement);
|
|
291
|
-
}
|
|
292
|
-
input.insertText(prompt);
|
|
293
|
-
syncComposerValue();
|
|
330
|
+
insertIntoComposer(composerInputRef.current, {text: prompt});
|
|
294
331
|
};
|
|
295
|
-
|
|
296
332
|
const insertMention = (item: (typeof MENTION_ITEMS)[number]) => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
value: `@${item.id}`,
|
|
309
|
-
label: item.label,
|
|
310
|
-
variant: 'cyan',
|
|
311
|
-
});
|
|
312
|
-
syncComposerValue();
|
|
333
|
+
insertIntoComposer(
|
|
334
|
+
composerInputRef.current,
|
|
335
|
+
{
|
|
336
|
+
token: {
|
|
337
|
+
value: `@${item.id}`,
|
|
338
|
+
label: item.label,
|
|
339
|
+
variant: 'cyan',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
true,
|
|
343
|
+
);
|
|
313
344
|
};
|
|
314
|
-
|
|
315
345
|
const insertModeToken = (label: string) => {
|
|
316
|
-
composerInputRef.current
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
346
|
+
insertIntoComposer(composerInputRef.current, {
|
|
347
|
+
token: {
|
|
348
|
+
value: label,
|
|
349
|
+
label,
|
|
350
|
+
variant: 'orange',
|
|
351
|
+
},
|
|
321
352
|
});
|
|
322
|
-
syncComposerValue();
|
|
323
353
|
};
|
|
324
354
|
|
|
325
355
|
return (
|
|
@@ -356,7 +386,7 @@ export default function AiChatLanding() {
|
|
|
356
386
|
setAttachments(prev => [
|
|
357
387
|
...prev,
|
|
358
388
|
...files.map(file => ({
|
|
359
|
-
id:
|
|
389
|
+
id: crypto.randomUUID(),
|
|
360
390
|
name: file.name,
|
|
361
391
|
})),
|
|
362
392
|
])
|
package/templates/ai-chat.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
3
|
// L > LC[p=0] > H > (SI[fill] > ChL > ChML > (ChM > ChB)*4 + ChC"Ask a follow up...") + (C.transparent > Tbar + S.section > MD) ;; Dlg#artifact[variant=fullscreen] > L > (DH"JWT Token Refresh" + (LC[p=0] > S.section > MD))
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {useState, type CSSProperties} from 'react';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
HStack,
|
|
@@ -40,6 +40,7 @@ import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
|
40
40
|
import {MoreMenu} from '@astryxdesign/core/MoreMenu';
|
|
41
41
|
import {Toolbar} from '@astryxdesign/core/Toolbar';
|
|
42
42
|
import {useResizable, ResizeHandle} from '@astryxdesign/core/Resizable';
|
|
43
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
43
44
|
|
|
44
45
|
import {
|
|
45
46
|
FileText,
|
|
@@ -52,19 +53,17 @@ import {
|
|
|
52
53
|
} from 'lucide-react';
|
|
53
54
|
|
|
54
55
|
// Below this container width the split-pane collapses to a single chat column
|
|
55
|
-
// and the artifact opens as a full-screen dialog instead.
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
56
|
+
// and the artifact opens as a full-screen dialog instead. Single source:
|
|
57
|
+
// MOBILE_MAX_WIDTH feeds useMediaQuery below, which both gates the panel and
|
|
58
|
+
// routes openArtifact — no container query, no manual width read to drift.
|
|
59
|
+
// 1023 keeps tablet widths from squeezing the chat into a ~100px rail beside
|
|
60
|
+
// the 640px default artifact panel.
|
|
59
61
|
const MOBILE_MAX_WIDTH = 1023;
|
|
60
|
-
|
|
61
62
|
const root: CSSProperties = {
|
|
62
63
|
// The viewer owns its chrome: the root fills the viewport and the message
|
|
63
64
|
// list plus artifact body scroll inside it (editor pattern).
|
|
64
65
|
height: '100dvh',
|
|
65
66
|
width: '100%',
|
|
66
|
-
containerType: 'inline-size',
|
|
67
|
-
containerName: 'artifact',
|
|
68
67
|
};
|
|
69
68
|
const chatColumn: CSSProperties = {
|
|
70
69
|
flex: 1,
|
|
@@ -91,38 +90,16 @@ const articleBody: CSSProperties = {
|
|
|
91
90
|
marginInline: 'auto',
|
|
92
91
|
};
|
|
93
92
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// plain <style> tag below so it needs NO CSS compiler.
|
|
93
|
+
// Width for the artifact panel. Plain style object — the panel unmounts below
|
|
94
|
+
// the breakpoint (see useMediaQuery in the component), so no container query
|
|
95
|
+
// or <style> tag is needed to hide it.
|
|
98
96
|
const artifactPanelWidthVar = (size: number | string): CSSProperties =>
|
|
99
97
|
({
|
|
100
|
-
|
|
98
|
+
width: typeof size === 'number' ? `${size}px` : size,
|
|
99
|
+
flexShrink: 0,
|
|
100
|
+
overflow: 'hidden',
|
|
101
101
|
}) as CSSProperties;
|
|
102
102
|
|
|
103
|
-
const AI_CHAT_CSS = `
|
|
104
|
-
.ai-chat-resize-handle {
|
|
105
|
-
display: flex;
|
|
106
|
-
}
|
|
107
|
-
.ai-chat-artifact-panel {
|
|
108
|
-
overflow: hidden;
|
|
109
|
-
display: flex;
|
|
110
|
-
flex-direction: column;
|
|
111
|
-
width: var(--artifact-panel-width);
|
|
112
|
-
flex-shrink: 0;
|
|
113
|
-
}
|
|
114
|
-
@container artifact (max-width: ${MOBILE_MAX_WIDTH}px) {
|
|
115
|
-
.ai-chat-resize-handle {
|
|
116
|
-
display: none;
|
|
117
|
-
}
|
|
118
|
-
.ai-chat-artifact-panel {
|
|
119
|
-
display: none;
|
|
120
|
-
width: 100%;
|
|
121
|
-
flex-shrink: 1;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
`;
|
|
125
|
-
|
|
126
103
|
// Artifact content
|
|
127
104
|
|
|
128
105
|
const MENTION_TOKENS = [
|
|
@@ -175,6 +152,13 @@ The refresh path is covered end to end:
|
|
|
175
152
|
|
|
176
153
|
// Artifact subviews
|
|
177
154
|
|
|
155
|
+
// Copy/Share live in both surfaces: desktop toolbar buttons and the mobile
|
|
156
|
+
// overflow menu. One items array so the two can't drift.
|
|
157
|
+
const ARTIFACT_ACTION_ITEMS = [
|
|
158
|
+
{label: 'Copy', icon: Copy},
|
|
159
|
+
{label: 'Share', icon: Share2},
|
|
160
|
+
] as const;
|
|
161
|
+
|
|
178
162
|
// Header actions: version menu, copy, share. Pass `onClose` for the desktop
|
|
179
163
|
// close button. A fragment so each control is a direct child of the toolbar.
|
|
180
164
|
function ArtifactActions({onClose}: {onClose?: () => void}) {
|
|
@@ -188,20 +172,16 @@ function ArtifactActions({onClose}: {onClose?: () => void}) {
|
|
|
188
172
|
}}
|
|
189
173
|
items={[{label: 'v2 (current)'}, {label: 'v1'}]}
|
|
190
174
|
/>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
size="sm"
|
|
202
|
-
icon={<Icon icon={Share2} size="sm" />}
|
|
203
|
-
isIconOnly
|
|
204
|
-
/>
|
|
175
|
+
{ARTIFACT_ACTION_ITEMS.map(item => (
|
|
176
|
+
<Button
|
|
177
|
+
key={item.label}
|
|
178
|
+
label={item.label}
|
|
179
|
+
variant="ghost"
|
|
180
|
+
size="sm"
|
|
181
|
+
icon={<Icon icon={item.icon} size="sm" />}
|
|
182
|
+
isIconOnly
|
|
183
|
+
/>
|
|
184
|
+
))}
|
|
205
185
|
{onClose != null && (
|
|
206
186
|
<Button
|
|
207
187
|
label="Close document"
|
|
@@ -226,23 +206,23 @@ function MobileArtifactActions() {
|
|
|
226
206
|
{
|
|
227
207
|
type: 'section',
|
|
228
208
|
title: 'Version',
|
|
229
|
-
items: [
|
|
230
|
-
{label: 'v2 (current)', onClick: () => {}},
|
|
231
|
-
{label: 'v1', onClick: () => {}},
|
|
232
|
-
],
|
|
209
|
+
items: [{label: 'v2 (current)'}, {label: 'v1'}],
|
|
233
210
|
},
|
|
234
211
|
{type: 'divider'},
|
|
235
|
-
|
|
236
|
-
{label: 'Share', icon: Share2},
|
|
212
|
+
...ARTIFACT_ACTION_ITEMS,
|
|
237
213
|
]}
|
|
238
214
|
/>
|
|
239
215
|
);
|
|
240
216
|
}
|
|
241
217
|
|
|
242
|
-
// Scrollable artifact
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
function
|
|
218
|
+
// Scrollable artifact content — the formatted document, no heading. The
|
|
219
|
+
// desktop panel renders ArtifactBody above it; the mobile dialog renders its
|
|
220
|
+
// title in DialogHeader instead, so both share this rather than a flag prop.
|
|
221
|
+
function ArtifactContent() {
|
|
222
|
+
return <Markdown>{ARTIFACT_CONTENT}</Markdown>;
|
|
223
|
+
}
|
|
224
|
+
// Desktop body: in-body heading plus the shared content.
|
|
225
|
+
function ArtifactBody() {
|
|
246
226
|
return (
|
|
247
227
|
<Section
|
|
248
228
|
variant="transparent"
|
|
@@ -251,13 +231,10 @@ function ArtifactBody({hasOwnTitle = true}: {hasOwnTitle?: boolean}) {
|
|
|
251
231
|
aria-label={ARTIFACT_TITLE}
|
|
252
232
|
tabIndex={0}>
|
|
253
233
|
<VStack gap={2} style={articleBody}>
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
</Heading>
|
|
259
|
-
)}
|
|
260
|
-
<Markdown>{ARTIFACT_CONTENT}</Markdown>
|
|
234
|
+
<Heading level={2} type="display-2">
|
|
235
|
+
{ARTIFACT_TITLE}
|
|
236
|
+
</Heading>
|
|
237
|
+
<ArtifactContent />
|
|
261
238
|
</VStack>
|
|
262
239
|
</Section>
|
|
263
240
|
);
|
|
@@ -298,7 +275,6 @@ export default function AiChat() {
|
|
|
298
275
|
// Mobile shows the artifact as a full-screen dialog; desktop as a side panel.
|
|
299
276
|
const [isArtifactDialogOpen, setIsArtifactDialogOpen] = useState(false);
|
|
300
277
|
const [isArtifactOpen, setIsArtifactOpen] = useState(true);
|
|
301
|
-
const rootRef = useRef<HTMLElement>(null);
|
|
302
278
|
const artifactResize = useResizable({
|
|
303
279
|
defaultSize: 640,
|
|
304
280
|
minSize: 480,
|
|
@@ -306,10 +282,12 @@ export default function AiChat() {
|
|
|
306
282
|
autoSaveId: 'ai-chat-artifact-panel',
|
|
307
283
|
});
|
|
308
284
|
|
|
309
|
-
//
|
|
285
|
+
// Single source for the breakpoint: the media query both gates the panel
|
|
286
|
+
// below and routes openArtifact, so the split-pane and the dialog target
|
|
287
|
+
// can't disagree about which surface is live.
|
|
288
|
+
const isMobileWidth = useMediaQuery(`(max-width: ${MOBILE_MAX_WIDTH}px)`);
|
|
310
289
|
const openArtifact = () => {
|
|
311
|
-
|
|
312
|
-
if (width <= MOBILE_MAX_WIDTH) {
|
|
290
|
+
if (isMobileWidth) {
|
|
313
291
|
setIsArtifactDialogOpen(true);
|
|
314
292
|
} else {
|
|
315
293
|
setIsArtifactOpen(true);
|
|
@@ -317,8 +295,7 @@ export default function AiChat() {
|
|
|
317
295
|
};
|
|
318
296
|
|
|
319
297
|
return (
|
|
320
|
-
<VStack
|
|
321
|
-
<style>{AI_CHAT_CSS}</style>
|
|
298
|
+
<VStack style={root}>
|
|
322
299
|
<Layout
|
|
323
300
|
height="fill"
|
|
324
301
|
content={
|
|
@@ -700,8 +677,11 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
700
677
|
</ChatLayout>
|
|
701
678
|
</VStack>
|
|
702
679
|
|
|
703
|
-
{/* Desktop split-pane: resize handle + artifact panel
|
|
704
|
-
|
|
680
|
+
{/* Desktop split-pane: resize handle + artifact panel. Gated on
|
|
681
|
+
the same media query that routes openArtifact, so shrinking
|
|
682
|
+
the window while open moves to the dialog instead of
|
|
683
|
+
squeezing the chat into a rail. */}
|
|
684
|
+
{isArtifactOpen && !isMobileWidth && (
|
|
705
685
|
<>
|
|
706
686
|
<ResizeHandle
|
|
707
687
|
direction="horizontal"
|
|
@@ -710,14 +690,12 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
710
690
|
pillPlacement="start"
|
|
711
691
|
hasDivider
|
|
712
692
|
label="Resize artifact panel"
|
|
713
|
-
className="ai-chat-resize-handle"
|
|
714
693
|
/>
|
|
715
694
|
|
|
716
695
|
{/* Toolbar as the card header, body below */}
|
|
717
696
|
<Card
|
|
718
697
|
variant="transparent"
|
|
719
698
|
height="100%"
|
|
720
|
-
className="ai-chat-artifact-panel"
|
|
721
699
|
style={artifactPanelWidthVar(artifactResize.size)}>
|
|
722
700
|
<Toolbar
|
|
723
701
|
label="Artifact actions"
|
|
@@ -773,7 +751,16 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
773
751
|
}
|
|
774
752
|
content={
|
|
775
753
|
<LayoutContent padding={0}>
|
|
776
|
-
<
|
|
754
|
+
<Section
|
|
755
|
+
variant="transparent"
|
|
756
|
+
style={artifactScroll}
|
|
757
|
+
role="region"
|
|
758
|
+
aria-label={ARTIFACT_TITLE}
|
|
759
|
+
tabIndex={0}>
|
|
760
|
+
<VStack gap={2} style={articleBody}>
|
|
761
|
+
<ArtifactContent />
|
|
762
|
+
</VStack>
|
|
763
|
+
</Section>
|
|
777
764
|
</LayoutContent>
|
|
778
765
|
}
|
|
779
766
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.
|
|
3
|
+
// L > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.5.0"[t=supporting]) + (H[j=center g=3] > B.primary"Embrace the night" + B.secondary"Read the lore")) + (S.transparent[p=0] > AR[ratio=16/9]))
|
|
4
4
|
|
|
5
5
|
import type {CSSProperties} from 'react';
|
|
6
6
|
import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
@@ -52,7 +52,7 @@ export default function CenteredHero() {
|
|
|
52
52
|
color="secondary"
|
|
53
53
|
justify="center"
|
|
54
54
|
hasTabularNumbers>
|
|
55
|
-
astryx-dracula v0.
|
|
55
|
+
astryx-dracula v0.5.0
|
|
56
56
|
</Text>
|
|
57
57
|
</VStack>
|
|
58
58
|
<HStack gap={3} wrap="wrap" justify="center">
|