@astryxdesign/cli 0.4.3-canary.71781fc → 0.4.3-canary.84ceb39
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/api/discover/_adapter.d.mts +5 -3
- package/api/discover/_adapter.mjs +6 -4
- package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.doc.mjs +21 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.tsx +46 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.doc.mjs +23 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.tsx +100 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.doc.mjs +22 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.tsx +47 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.doc.mjs +22 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.tsx +51 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.doc.mjs +26 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.tsx +221 -0
- package/authoring/doctypes/base/type.ts +9 -0
- package/clients/cli/commands/discover.broken-integration.test.mjs +112 -0
- package/clients/cli/commands/discover.mjs +12 -0
- package/clients/cli/commands/search.mjs +9 -0
- package/foundation/integrations/integration-warnings.test.mjs +17 -0
- package/foundation/integrations/validate-contributions.mjs +7 -0
- package/package.json +9 -9
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* External packages come from configured integrations that declare a components
|
|
8
8
|
* root; each becomes a scannable package keyed by its docsDir. `configured`
|
|
9
|
-
* reports whether
|
|
10
|
-
*
|
|
11
|
-
* nothing
|
|
9
|
+
* reports whether the project configured ANY integration — including one whose
|
|
10
|
+
* manifest failed to load, which contributes nothing but is emphatically not
|
|
11
|
+
* "nothing configured". It lets an empty result distinguish "nothing
|
|
12
|
+
* configured" (`false`) from "configured but nothing discovered" (`true`),
|
|
13
|
+
* which the list leaf surfaces as `meta`.
|
|
12
14
|
*
|
|
13
15
|
* @returns {Promise<{packages: ScannedPackage[], configured: boolean}>}
|
|
14
16
|
*/
|
|
@@ -62,9 +62,11 @@ function validateDocs(docs) {
|
|
|
62
62
|
*
|
|
63
63
|
* External packages come from configured integrations that declare a components
|
|
64
64
|
* root; each becomes a scannable package keyed by its docsDir. `configured`
|
|
65
|
-
* reports whether
|
|
66
|
-
*
|
|
67
|
-
* nothing
|
|
65
|
+
* reports whether the project configured ANY integration — including one whose
|
|
66
|
+
* manifest failed to load, which contributes nothing but is emphatically not
|
|
67
|
+
* "nothing configured". It lets an empty result distinguish "nothing
|
|
68
|
+
* configured" (`false`) from "configured but nothing discovered" (`true`),
|
|
69
|
+
* which the list leaf surfaces as `meta`.
|
|
68
70
|
*
|
|
69
71
|
* @returns {Promise<{packages: ScannedPackage[], configured: boolean}>}
|
|
70
72
|
*/
|
|
@@ -84,7 +86,7 @@ export async function discoverPackages() {
|
|
|
84
86
|
docsDir: integration.components,
|
|
85
87
|
}));
|
|
86
88
|
if (explicitPackages.length === 0) {
|
|
87
|
-
return {packages: [], configured:
|
|
89
|
+
return {packages: [], configured: loadedIntegrations.length > 0};
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
const packages = scanAllPackages(
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — Height variants',
|
|
8
|
+
displayName: 'Bottom Sheet — Height variants',
|
|
9
|
+
description:
|
|
10
|
+
'Compares hug, capped, and tall starting heights for different amounts of content.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Stack',
|
|
19
|
+
'Text',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {
|
|
7
|
+
BottomSheet,
|
|
8
|
+
type BottomSheetHeight,
|
|
9
|
+
} from '@astryxdesign/core/BottomSheet';
|
|
10
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
11
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
12
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
13
|
+
import {HStack, VStack} from '@astryxdesign/core/Stack';
|
|
14
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
15
|
+
|
|
16
|
+
const descriptions: Record<BottomSheetHeight, string> = {
|
|
17
|
+
hug: 'Hug fits short, bounded content.',
|
|
18
|
+
capped: 'Capped starts at a comfortable mid-height for lists and filters.',
|
|
19
|
+
tall: 'Tall reserves most of the viewport for long or changing content.',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function BottomSheetHeights() {
|
|
23
|
+
const [height, setHeight] = useState<BottomSheetHeight | null>(null);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<HStack gap={2} wrap="wrap">
|
|
28
|
+
<Button label="Open hug" onClick={() => setHeight('hug')} />
|
|
29
|
+
<Button label="Open capped" onClick={() => setHeight('capped')} />
|
|
30
|
+
<Button label="Open tall" onClick={() => setHeight('tall')} />
|
|
31
|
+
</HStack>
|
|
32
|
+
<BottomSheet
|
|
33
|
+
isOpen={height != null}
|
|
34
|
+
onOpenChange={isOpen => !isOpen && setHeight(null)}
|
|
35
|
+
label={`${height ?? 'Hug'} height`}
|
|
36
|
+
height={height ?? 'hug'}>
|
|
37
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
38
|
+
<Heading level={3}>{height ?? 'Hug'} height</Heading>
|
|
39
|
+
<Divider />
|
|
40
|
+
<Text type="body">{descriptions[height ?? 'hug']}</Text>
|
|
41
|
+
<Button label="Close" onClick={() => setHeight(null)} />
|
|
42
|
+
</VStack>
|
|
43
|
+
</BottomSheet>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — Mobile keyboard',
|
|
8
|
+
displayName: 'Bottom Sheet — Mobile keyboard',
|
|
9
|
+
description:
|
|
10
|
+
'Uses a tall, scrollable form that keeps focused controls visible above the mobile keyboard.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Stack',
|
|
19
|
+
'Text',
|
|
20
|
+
'TextArea',
|
|
21
|
+
'TextInput',
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
9
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
10
|
+
import {VStack} from '@astryxdesign/core/Stack';
|
|
11
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
12
|
+
import {TextArea} from '@astryxdesign/core/TextArea';
|
|
13
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
14
|
+
|
|
15
|
+
interface ProfileFormValues {
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
company: string;
|
|
19
|
+
role: string;
|
|
20
|
+
bio: string;
|
|
21
|
+
notes: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const initialValues: ProfileFormValues = {
|
|
25
|
+
name: '',
|
|
26
|
+
email: '',
|
|
27
|
+
company: '',
|
|
28
|
+
role: '',
|
|
29
|
+
bio: '',
|
|
30
|
+
notes: '',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default function BottomSheetMobileKeyboard() {
|
|
34
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
35
|
+
const [values, setValues] = useState(initialValues);
|
|
36
|
+
const update =
|
|
37
|
+
(field: keyof ProfileFormValues) =>
|
|
38
|
+
(value: string): void =>
|
|
39
|
+
setValues(current => ({...current, [field]: value}));
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<Button label="Edit profile" onClick={() => setIsOpen(true)} />
|
|
44
|
+
<BottomSheet
|
|
45
|
+
isOpen={isOpen}
|
|
46
|
+
onOpenChange={setIsOpen}
|
|
47
|
+
label="Edit profile"
|
|
48
|
+
height="tall">
|
|
49
|
+
<form
|
|
50
|
+
onSubmit={event => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
setIsOpen(false);
|
|
53
|
+
}}>
|
|
54
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
55
|
+
<Heading level={3}>Edit profile</Heading>
|
|
56
|
+
<Divider />
|
|
57
|
+
<Text type="supporting" color="secondary">
|
|
58
|
+
Focus fields throughout the form to see them remain visible above
|
|
59
|
+
the mobile keyboard.
|
|
60
|
+
</Text>
|
|
61
|
+
<TextInput
|
|
62
|
+
label="Name"
|
|
63
|
+
value={values.name}
|
|
64
|
+
onChange={update('name')}
|
|
65
|
+
/>
|
|
66
|
+
<TextInput
|
|
67
|
+
label="Email"
|
|
68
|
+
type="email"
|
|
69
|
+
value={values.email}
|
|
70
|
+
onChange={update('email')}
|
|
71
|
+
/>
|
|
72
|
+
<TextInput
|
|
73
|
+
label="Company"
|
|
74
|
+
value={values.company}
|
|
75
|
+
onChange={update('company')}
|
|
76
|
+
/>
|
|
77
|
+
<TextInput
|
|
78
|
+
label="Role"
|
|
79
|
+
value={values.role}
|
|
80
|
+
onChange={update('role')}
|
|
81
|
+
/>
|
|
82
|
+
<TextArea
|
|
83
|
+
label="Bio"
|
|
84
|
+
rows={5}
|
|
85
|
+
value={values.bio}
|
|
86
|
+
onChange={update('bio')}
|
|
87
|
+
/>
|
|
88
|
+
<TextArea
|
|
89
|
+
label="Notes"
|
|
90
|
+
rows={5}
|
|
91
|
+
value={values.notes}
|
|
92
|
+
onChange={update('notes')}
|
|
93
|
+
/>
|
|
94
|
+
<Button label="Save profile" type="submit" />
|
|
95
|
+
</VStack>
|
|
96
|
+
</form>
|
|
97
|
+
</BottomSheet>
|
|
98
|
+
</>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — No scrim',
|
|
8
|
+
displayName: 'Bottom Sheet — No scrim',
|
|
9
|
+
description:
|
|
10
|
+
'Keeps the page visible and interactive behind a non-modal bottom sheet.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Section',
|
|
19
|
+
'Stack',
|
|
20
|
+
'Text',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
9
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
10
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
11
|
+
import {VStack} from '@astryxdesign/core/Stack';
|
|
12
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
13
|
+
|
|
14
|
+
export default function BottomSheetNoScrim() {
|
|
15
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [backgroundClicks, setBackgroundClicks] = useState(0);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Section padding={4}>
|
|
20
|
+
<VStack gap={3}>
|
|
21
|
+
<Heading level={3}>Nearby places</Heading>
|
|
22
|
+
<Text type="body">Background interactions: {backgroundClicks}</Text>
|
|
23
|
+
<Button
|
|
24
|
+
label="Interact with page"
|
|
25
|
+
variant="secondary"
|
|
26
|
+
onClick={() => setBackgroundClicks(count => count + 1)}
|
|
27
|
+
/>
|
|
28
|
+
<Button label="Show place details" onClick={() => setIsOpen(true)} />
|
|
29
|
+
</VStack>
|
|
30
|
+
<BottomSheet
|
|
31
|
+
isOpen={isOpen}
|
|
32
|
+
onOpenChange={setIsOpen}
|
|
33
|
+
label="Place details"
|
|
34
|
+
height="hug"
|
|
35
|
+
hasScrim={false}>
|
|
36
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
37
|
+
<Heading level={3}>Central Park</Heading>
|
|
38
|
+
<Divider />
|
|
39
|
+
<Text type="body">
|
|
40
|
+
The page remains visible and interactive behind this sheet.
|
|
41
|
+
</Text>
|
|
42
|
+
<Button label="Close details" onClick={() => setIsOpen(false)} />
|
|
43
|
+
</VStack>
|
|
44
|
+
</BottomSheet>
|
|
45
|
+
</Section>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet',
|
|
8
|
+
displayName: 'Bottom Sheet',
|
|
9
|
+
description: 'A mobile filter surface that rises from the bottom edge.',
|
|
10
|
+
isReady: true,
|
|
11
|
+
isShowcase: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'CheckboxInput',
|
|
17
|
+
'Divider',
|
|
18
|
+
'Heading',
|
|
19
|
+
'Section',
|
|
20
|
+
'Stack',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
9
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
10
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
11
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
12
|
+
import {VStack} from '@astryxdesign/core/Stack';
|
|
13
|
+
|
|
14
|
+
export default function BottomSheetShowcase() {
|
|
15
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [inStock, setInStock] = useState(false);
|
|
17
|
+
const [onSale, setOnSale] = useState(false);
|
|
18
|
+
const [freeShipping, setFreeShipping] = useState(false);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Button label="Open sheet" onClick={() => setIsOpen(true)} />
|
|
23
|
+
<BottomSheet isOpen={isOpen} onOpenChange={setIsOpen} label="Filters">
|
|
24
|
+
<Section padding={4}>
|
|
25
|
+
<VStack gap={4}>
|
|
26
|
+
<Heading level={3}>Filters</Heading>
|
|
27
|
+
<Divider />
|
|
28
|
+
<VStack gap={2}>
|
|
29
|
+
<CheckboxInput
|
|
30
|
+
label="In stock"
|
|
31
|
+
value={inStock}
|
|
32
|
+
onChange={setInStock}
|
|
33
|
+
/>
|
|
34
|
+
<CheckboxInput
|
|
35
|
+
label="On sale"
|
|
36
|
+
value={onSale}
|
|
37
|
+
onChange={setOnSale}
|
|
38
|
+
/>
|
|
39
|
+
<CheckboxInput
|
|
40
|
+
label="Free shipping"
|
|
41
|
+
value={freeShipping}
|
|
42
|
+
onChange={setFreeShipping}
|
|
43
|
+
/>
|
|
44
|
+
</VStack>
|
|
45
|
+
<Button label="Apply" onClick={() => setIsOpen(false)} />
|
|
46
|
+
</VStack>
|
|
47
|
+
</Section>
|
|
48
|
+
</BottomSheet>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BottomSheetSwitcher',
|
|
7
|
+
name: 'Bottom Sheet Switcher',
|
|
8
|
+
displayName: 'Bottom Sheet Switcher',
|
|
9
|
+
description:
|
|
10
|
+
'A three-step flow that transitions between content-hugging sheets of different heights inside one shared dialog.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: true,
|
|
13
|
+
aspectRatio: 3 / 4,
|
|
14
|
+
componentsUsed: [
|
|
15
|
+
'BottomSheet',
|
|
16
|
+
'BottomSheetSwitcher',
|
|
17
|
+
'Button',
|
|
18
|
+
'CheckboxInput',
|
|
19
|
+
'Divider',
|
|
20
|
+
'Heading',
|
|
21
|
+
'RadioList',
|
|
22
|
+
'Section',
|
|
23
|
+
'Stack',
|
|
24
|
+
'Text',
|
|
25
|
+
],
|
|
26
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet, BottomSheetSwitcher} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
9
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
10
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
11
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
12
|
+
import {HStack, VStack} from '@astryxdesign/core/Stack';
|
|
13
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
14
|
+
import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
|
|
15
|
+
|
|
16
|
+
type NotificationSheetHeight = 'hug' | 'capped';
|
|
17
|
+
|
|
18
|
+
interface NotificationOverviewSheetProps {
|
|
19
|
+
height: NotificationSheetHeight;
|
|
20
|
+
onCancel: () => void;
|
|
21
|
+
onContinue: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function NotificationOverviewSheet({
|
|
25
|
+
height,
|
|
26
|
+
onCancel,
|
|
27
|
+
onContinue,
|
|
28
|
+
}: NotificationOverviewSheetProps) {
|
|
29
|
+
return (
|
|
30
|
+
<BottomSheet
|
|
31
|
+
sheetId="overview"
|
|
32
|
+
label="Set up notifications"
|
|
33
|
+
height={height}>
|
|
34
|
+
<Section padding={4}>
|
|
35
|
+
<VStack gap={4}>
|
|
36
|
+
<VStack gap={1}>
|
|
37
|
+
<Heading level={3}>Set up notifications</Heading>
|
|
38
|
+
<Text type="supporting" color="secondary">
|
|
39
|
+
Step 1 of 3
|
|
40
|
+
</Text>
|
|
41
|
+
</VStack>
|
|
42
|
+
<Divider />
|
|
43
|
+
<Text type="supporting" color="secondary">
|
|
44
|
+
Stay informed about activity that matters without checking back
|
|
45
|
+
throughout the day.
|
|
46
|
+
</Text>
|
|
47
|
+
<VStack gap={3}>
|
|
48
|
+
<VStack gap={1}>
|
|
49
|
+
<Text type="label">Important activity</Text>
|
|
50
|
+
<Text type="supporting" color="secondary">
|
|
51
|
+
Know when someone mentions you or needs your attention.
|
|
52
|
+
</Text>
|
|
53
|
+
</VStack>
|
|
54
|
+
<VStack gap={1}>
|
|
55
|
+
<Text type="label">Timely reminders</Text>
|
|
56
|
+
<Text type="supporting" color="secondary">
|
|
57
|
+
Get a reminder before work reaches its due date.
|
|
58
|
+
</Text>
|
|
59
|
+
</VStack>
|
|
60
|
+
<VStack gap={1}>
|
|
61
|
+
<Text type="label">Useful summaries</Text>
|
|
62
|
+
<Text type="supporting" color="secondary">
|
|
63
|
+
Catch up on anything you may have missed.
|
|
64
|
+
</Text>
|
|
65
|
+
</VStack>
|
|
66
|
+
</VStack>
|
|
67
|
+
<HStack gap={2} hAlign="end">
|
|
68
|
+
<Button label="Cancel" variant="secondary" onClick={onCancel} />
|
|
69
|
+
<Button label="Continue" onClick={onContinue} />
|
|
70
|
+
</HStack>
|
|
71
|
+
</VStack>
|
|
72
|
+
</Section>
|
|
73
|
+
</BottomSheet>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface NotificationFrequencySheetProps {
|
|
78
|
+
height: NotificationSheetHeight;
|
|
79
|
+
onBack: () => void;
|
|
80
|
+
onContinue: () => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function NotificationFrequencySheet({
|
|
84
|
+
height,
|
|
85
|
+
onBack,
|
|
86
|
+
onContinue,
|
|
87
|
+
}: NotificationFrequencySheetProps) {
|
|
88
|
+
const [frequency, setFrequency] = useState('daily');
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<BottomSheet
|
|
92
|
+
sheetId="frequency"
|
|
93
|
+
label="Notification frequency"
|
|
94
|
+
height={height}>
|
|
95
|
+
<Section padding={4}>
|
|
96
|
+
<VStack gap={4}>
|
|
97
|
+
<VStack gap={1}>
|
|
98
|
+
<Heading level={3}>How often?</Heading>
|
|
99
|
+
<Text type="supporting" color="secondary">
|
|
100
|
+
Step 2 of 3
|
|
101
|
+
</Text>
|
|
102
|
+
</VStack>
|
|
103
|
+
<Divider />
|
|
104
|
+
<RadioList
|
|
105
|
+
label="Notification frequency"
|
|
106
|
+
isLabelHidden
|
|
107
|
+
value={frequency}
|
|
108
|
+
onChange={setFrequency}>
|
|
109
|
+
<RadioListItem label="Immediately" value="immediately" />
|
|
110
|
+
<RadioListItem label="Daily" value="daily" />
|
|
111
|
+
<RadioListItem label="Weekly" value="weekly" />
|
|
112
|
+
</RadioList>
|
|
113
|
+
<HStack gap={2} hAlign="end">
|
|
114
|
+
<Button label="Back" variant="secondary" onClick={onBack} />
|
|
115
|
+
<Button label="Continue" onClick={onContinue} />
|
|
116
|
+
</HStack>
|
|
117
|
+
</VStack>
|
|
118
|
+
</Section>
|
|
119
|
+
</BottomSheet>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface NotificationChannelsSheetProps {
|
|
124
|
+
height: NotificationSheetHeight;
|
|
125
|
+
onBack: () => void;
|
|
126
|
+
onFinish: () => void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function NotificationChannelsSheet({
|
|
130
|
+
height,
|
|
131
|
+
onBack,
|
|
132
|
+
onFinish,
|
|
133
|
+
}: NotificationChannelsSheetProps) {
|
|
134
|
+
const [email, setEmail] = useState(true);
|
|
135
|
+
const [pushNotifications, setPushNotifications] = useState(true);
|
|
136
|
+
const [textMessages, setTextMessages] = useState(false);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<BottomSheet
|
|
140
|
+
sheetId="channels"
|
|
141
|
+
label="Notification channels"
|
|
142
|
+
height={height}>
|
|
143
|
+
<Section padding={4}>
|
|
144
|
+
<VStack gap={4}>
|
|
145
|
+
<VStack gap={1}>
|
|
146
|
+
<Heading level={3}>Where should we notify you?</Heading>
|
|
147
|
+
<Text type="supporting" color="secondary">
|
|
148
|
+
Step 3 of 3
|
|
149
|
+
</Text>
|
|
150
|
+
</VStack>
|
|
151
|
+
<Divider />
|
|
152
|
+
<Text type="supporting" color="secondary">
|
|
153
|
+
Choose any combination. You can change these preferences later.
|
|
154
|
+
</Text>
|
|
155
|
+
<VStack gap={2}>
|
|
156
|
+
<CheckboxInput label="Email" value={email} onChange={setEmail} />
|
|
157
|
+
<CheckboxInput
|
|
158
|
+
label="Push notifications"
|
|
159
|
+
value={pushNotifications}
|
|
160
|
+
onChange={setPushNotifications}
|
|
161
|
+
/>
|
|
162
|
+
<CheckboxInput
|
|
163
|
+
label="Text messages"
|
|
164
|
+
value={textMessages}
|
|
165
|
+
onChange={setTextMessages}
|
|
166
|
+
/>
|
|
167
|
+
</VStack>
|
|
168
|
+
<HStack gap={2} hAlign="end">
|
|
169
|
+
<Button label="Back" variant="secondary" onClick={onBack} />
|
|
170
|
+
<Button label="Finish" onClick={onFinish} />
|
|
171
|
+
</HStack>
|
|
172
|
+
</VStack>
|
|
173
|
+
</Section>
|
|
174
|
+
</BottomSheet>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface MultiStepSwitcherExampleProps {
|
|
179
|
+
height: NotificationSheetHeight;
|
|
180
|
+
hasScrim?: boolean;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function MultiStepSwitcherExample({
|
|
184
|
+
height,
|
|
185
|
+
hasScrim = true,
|
|
186
|
+
}: MultiStepSwitcherExampleProps) {
|
|
187
|
+
const [activeSheet, setActiveSheet] = useState<string | null>(null);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<>
|
|
191
|
+
<Button
|
|
192
|
+
label="Set up notifications"
|
|
193
|
+
onClick={() => setActiveSheet('overview')}
|
|
194
|
+
/>
|
|
195
|
+
<BottomSheetSwitcher
|
|
196
|
+
activeSheet={activeSheet}
|
|
197
|
+
onActiveSheetChange={setActiveSheet}
|
|
198
|
+
hasScrim={hasScrim}>
|
|
199
|
+
<NotificationOverviewSheet
|
|
200
|
+
height={height}
|
|
201
|
+
onCancel={() => setActiveSheet(null)}
|
|
202
|
+
onContinue={() => setActiveSheet('frequency')}
|
|
203
|
+
/>
|
|
204
|
+
<NotificationFrequencySheet
|
|
205
|
+
height={height}
|
|
206
|
+
onBack={() => setActiveSheet('overview')}
|
|
207
|
+
onContinue={() => setActiveSheet('channels')}
|
|
208
|
+
/>
|
|
209
|
+
<NotificationChannelsSheet
|
|
210
|
+
height={height}
|
|
211
|
+
onBack={() => setActiveSheet('frequency')}
|
|
212
|
+
onFinish={() => setActiveSheet(null)}
|
|
213
|
+
/>
|
|
214
|
+
</BottomSheetSwitcher>
|
|
215
|
+
</>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export default function BottomSheetSwitcherShowcase() {
|
|
220
|
+
return <MultiStepSwitcherExample height="hug" />;
|
|
221
|
+
}
|
|
@@ -117,6 +117,15 @@ export interface ComponentPlaygroundConfig {
|
|
|
117
117
|
* the component is visible on load and knobs stay usable, whereas a real
|
|
118
118
|
* top-layer modal makes the rest of the page inert (#3657). */
|
|
119
119
|
overlay?: boolean;
|
|
120
|
+
/** Override the controlled prop and open value used by an overlay preview.
|
|
121
|
+
* Use with `overlay: true` when the component does not use `isOpen: true`
|
|
122
|
+
* to represent its open state. */
|
|
123
|
+
overlayControl?: {
|
|
124
|
+
/** Controlled prop that determines whether the overlay is open. */
|
|
125
|
+
stateProp: string;
|
|
126
|
+
/** Value assigned to stateProp when the preview's open button is used. */
|
|
127
|
+
openValue: unknown;
|
|
128
|
+
};
|
|
120
129
|
/** The component reads AppShell mobile context and renders nothing
|
|
121
130
|
* without it (e.g. `MobileNavToggle` returns null unless the context
|
|
122
131
|
* reports an enabled mobile viewport — the default value outside
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `astryx discover` and `astryx search` against an integration whose
|
|
5
|
+
* manifest fails to load.
|
|
6
|
+
*
|
|
7
|
+
* A manifest authored against a removed API throws on import, contributes
|
|
8
|
+
* nothing, and used to leave discover reporting "No integrations configured."
|
|
9
|
+
* — the package simply vanished. These drive the real CLI in-process against a
|
|
10
|
+
* hermetic project (astryx.config.mjs + a throwing astryx.integration.mjs under
|
|
11
|
+
* node_modules) and pin the loud behavior.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import * as os from 'node:os';
|
|
18
|
+
import {fileURLToPath} from 'node:url';
|
|
19
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
20
|
+
|
|
21
|
+
const CORE_DIR = path.resolve(
|
|
22
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
23
|
+
'../../../../core',
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
let tmpDir;
|
|
27
|
+
let project;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Configure `@test/broken` with a manifest that throws at import. Calling an
|
|
31
|
+
* undeclared factory stands in for the real failure this came from: a 0.2.x
|
|
32
|
+
* manifest still calling `createIntegration`, which 0.3.0 removed.
|
|
33
|
+
*/
|
|
34
|
+
function buildBrokenIntegration() {
|
|
35
|
+
const intDir = path.join(project, 'node_modules', '@test', 'broken');
|
|
36
|
+
fs.mkdirSync(path.join(intDir, 'components'), {recursive: true});
|
|
37
|
+
fs.writeFileSync(
|
|
38
|
+
path.join(intDir, 'package.json'),
|
|
39
|
+
JSON.stringify({name: '@test/broken', version: '1.2.3'}),
|
|
40
|
+
);
|
|
41
|
+
fs.writeFileSync(
|
|
42
|
+
path.join(intDir, 'astryx.integration.mjs'),
|
|
43
|
+
`export default createIntegration({components: './components'});\n`,
|
|
44
|
+
);
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(project, 'astryx.config.mjs'),
|
|
47
|
+
`export default {integrations: ['@test/broken']};\n`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-discover-broken-'));
|
|
53
|
+
project = path.join(tmpDir, 'project');
|
|
54
|
+
fs.mkdirSync(project, {recursive: true});
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
path.join(project, 'package.json'),
|
|
57
|
+
JSON.stringify({name: 'proj', version: '1.0.0'}),
|
|
58
|
+
);
|
|
59
|
+
buildBrokenIntegration();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('astryx discover with a manifest that fails to load', () => {
|
|
67
|
+
it('warns on stderr and does not claim nothing is configured', async () => {
|
|
68
|
+
const {status, stdout, stderr} = await runCli(['discover'], {cwd: project});
|
|
69
|
+
|
|
70
|
+
expect(status).toBe(0);
|
|
71
|
+
expect(stderr).toContain(
|
|
72
|
+
'Warning: @test/broken has 1 integration issue(s). ' +
|
|
73
|
+
'Run: astryx validate-integration @test/broken',
|
|
74
|
+
);
|
|
75
|
+
expect(stdout).not.toContain('No integrations configured.');
|
|
76
|
+
expect(stdout).toContain('No external components found in configured integrations.');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('reports meta.configured=true in --json, with the nudge suppressed', async () => {
|
|
80
|
+
const {status, stdout, stderr} = await runCli(['discover', '--json'], {
|
|
81
|
+
cwd: project,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(status).toBe(0);
|
|
85
|
+
expect(JSON.parse(stdout).meta).toEqual({configured: true});
|
|
86
|
+
expect(stderr).not.toContain('integration issue');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('astryx search with a manifest that fails to load', () => {
|
|
91
|
+
// search needs a resolvable @astryxdesign/core; without one it errors out
|
|
92
|
+
// before it can list anything, which is not the case under test.
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
const scope = path.join(project, 'node_modules', '@astryxdesign');
|
|
95
|
+
fs.mkdirSync(scope, {recursive: true});
|
|
96
|
+
fs.symlinkSync(CORE_DIR, path.join(scope, 'core'), 'dir');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('warns on stderr, and suppresses the nudge under --json', async () => {
|
|
100
|
+
const {status, stderr} = await runCli(['search', 'button'], {cwd: project});
|
|
101
|
+
|
|
102
|
+
expect(status).toBe(0);
|
|
103
|
+
expect(stderr).toContain(
|
|
104
|
+
'Warning: @test/broken has 1 integration issue(s). ' +
|
|
105
|
+
'Run: astryx validate-integration @test/broken',
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const asJson = await runCli(['search', 'button', '--json'], {cwd: project});
|
|
109
|
+
expect(asJson.status).toBe(0);
|
|
110
|
+
expect(asJson.stderr).not.toContain('integration issue');
|
|
111
|
+
}, 30_000);
|
|
112
|
+
});
|
|
@@ -15,6 +15,8 @@ import {jsonOut} from '../../../foundation/response/json.mjs';
|
|
|
15
15
|
import {emit, section, text, record, records, list, code} from '../formatters/index.mjs';
|
|
16
16
|
import {cliError} from '../lib/cli-error.mjs';
|
|
17
17
|
import {discover as discoverApi} from '../../../api/discover/discover.mjs';
|
|
18
|
+
import {Project} from '../../../foundation/config/project.mjs';
|
|
19
|
+
import {warnOnIntegrationIssues} from '../../../foundation/integrations/integration-warnings.mjs';
|
|
18
20
|
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
19
21
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
20
22
|
import {doc as discoverCommand} from './discover.doc.mjs';
|
|
@@ -41,6 +43,16 @@ export function registerDiscover(program) {
|
|
|
41
43
|
const zh = program.opts().zh || false;
|
|
42
44
|
const run = getCliInvocation();
|
|
43
45
|
|
|
46
|
+
// Non-blocking nudge: if any configured integration has validation
|
|
47
|
+
// issues, print one compact line to stderr pointing at
|
|
48
|
+
// validate-integration. Best-effort; suppressed in --json mode.
|
|
49
|
+
try {
|
|
50
|
+
const project = await Project.load(process.cwd());
|
|
51
|
+
await warnOnIntegrationIssues(project.loadedIntegrations, {json});
|
|
52
|
+
} catch {
|
|
53
|
+
// Never let the nudge break the command.
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
let result;
|
|
45
57
|
try {
|
|
46
58
|
result = await discoverApi(query, {components: options.components, lang, zh});
|
|
@@ -26,6 +26,8 @@ import {emit, section, text, records} from '../formatters/index.mjs';
|
|
|
26
26
|
import {cliError} from '../lib/cli-error.mjs';
|
|
27
27
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
28
28
|
import {search as searchApi} from '../../../api/search/search.mjs';
|
|
29
|
+
import {Project} from '../../../foundation/config/project.mjs';
|
|
30
|
+
import {warnOnIntegrationIssues} from '../../../foundation/integrations/integration-warnings.mjs';
|
|
29
31
|
import {doc as searchCommand} from './search.doc.mjs';
|
|
30
32
|
import {doc as searchFn} from '../../../api/search/search.doc.mjs';
|
|
31
33
|
|
|
@@ -41,6 +43,13 @@ export function registerSearch(program) {
|
|
|
41
43
|
) => {
|
|
42
44
|
const json = program.opts().json || false;
|
|
43
45
|
|
|
46
|
+
try {
|
|
47
|
+
const project = await Project.load(process.cwd());
|
|
48
|
+
await warnOnIntegrationIssues(project.loadedIntegrations, {json});
|
|
49
|
+
} catch {
|
|
50
|
+
// Never let the nudge break the command.
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
// Parse --limit to a number; the API validates it (positive integer) and
|
|
45
54
|
// throws ERR_INVALID_ARGUMENT, so we pass NaN through rather than
|
|
46
55
|
// pre-rejecting with a generic code here.
|
|
@@ -68,6 +68,23 @@ describe('warnOnIntegrationIssues', () => {
|
|
|
68
68
|
expect(errLines).toHaveLength(0);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it('warns for a manifest that failed to load, which declares no roots', async () => {
|
|
72
|
+
// loadIntegrations records __loadError and no contribution roots, so every
|
|
73
|
+
// on-disk check finds nothing — without the load error itself counting as
|
|
74
|
+
// an issue, a stale manifest is silent everywhere.
|
|
75
|
+
const integration = {
|
|
76
|
+
...loaded({name: '@acme/stale'}),
|
|
77
|
+
__loadError: 'createIntegration is not a function',
|
|
78
|
+
};
|
|
79
|
+
await warnOnIntegrationIssues([integration], {json: false});
|
|
80
|
+
|
|
81
|
+
expect(errLines).toHaveLength(1);
|
|
82
|
+
expect(errLines[0]).toBe(
|
|
83
|
+
'Warning: @acme/stale has 1 integration issue(s). ' +
|
|
84
|
+
'Run: astryx validate-integration @acme/stale',
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
71
88
|
it('emits nothing for an integration with no issues', async () => {
|
|
72
89
|
// Existing components root with no broken contributions → no issues.
|
|
73
90
|
const componentsRoot = path.join(tmpDir, 'components');
|
|
@@ -149,6 +149,13 @@ export async function validateLoadedIntegration(loaded) {
|
|
|
149
149
|
/** @type {Issue[]} */
|
|
150
150
|
const issues = [];
|
|
151
151
|
if (!loaded || typeof loaded !== 'object') return issues;
|
|
152
|
+
// A manifest that threw on import (or failed the schema) carries a load-error
|
|
153
|
+
// marker and no contribution roots, so every check below would find nothing
|
|
154
|
+
// and report a clean integration — which is how a stale manifest used to go
|
|
155
|
+
// silently invisible. The load error IS the issue.
|
|
156
|
+
if (loaded.__loadError) {
|
|
157
|
+
return [issueError('integration_error', loaded.__loadError)];
|
|
158
|
+
}
|
|
152
159
|
checkRoots(
|
|
153
160
|
{
|
|
154
161
|
components: loaded.components,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.4.3-canary.
|
|
3
|
+
"version": "0.4.3-canary.84ceb39",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"zod": "^4.4.3"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@astryxdesign/charts": "0.4.3-canary.
|
|
91
|
-
"@astryxdesign/core": "0.4.3-canary.
|
|
92
|
-
"@astryxdesign/lab": "0.4.3-canary.
|
|
93
|
-
"@astryxdesign/theme-neutral": "0.4.3-canary.
|
|
90
|
+
"@astryxdesign/charts": "0.4.3-canary.84ceb39",
|
|
91
|
+
"@astryxdesign/core": "0.4.3-canary.84ceb39",
|
|
92
|
+
"@astryxdesign/lab": "0.4.3-canary.84ceb39",
|
|
93
|
+
"@astryxdesign/theme-neutral": "0.4.3-canary.84ceb39",
|
|
94
94
|
"gpt-tokenizer": "^3.4.0"
|
|
95
95
|
},
|
|
96
96
|
"peerDependenciesMeta": {
|
|
@@ -108,10 +108,10 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
|
-
"@astryxdesign/charts": "0.4.3-canary.
|
|
112
|
-
"@astryxdesign/core": "0.4.3-canary.
|
|
113
|
-
"@astryxdesign/lab": "0.4.3-canary.
|
|
114
|
-
"@astryxdesign/theme-neutral": "0.4.3-canary.
|
|
111
|
+
"@astryxdesign/charts": "0.4.3-canary.84ceb39",
|
|
112
|
+
"@astryxdesign/core": "0.4.3-canary.84ceb39",
|
|
113
|
+
"@astryxdesign/lab": "0.4.3-canary.84ceb39",
|
|
114
|
+
"@astryxdesign/theme-neutral": "0.4.3-canary.84ceb39",
|
|
115
115
|
"gpt-tokenizer": "^3.4.0"
|
|
116
116
|
},
|
|
117
117
|
"scripts": {
|