@ossy/package-catalog 1.0.1
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 +33 -0
- package/src/ActionCapabilityCard.jsx +87 -0
- package/src/ActionExamplesPanel.jsx +47 -0
- package/src/CatalogPageStyles.jsx +15 -0
- package/src/Definition.js +9 -0
- package/src/HowToCard.jsx +79 -0
- package/src/ManifestSection.jsx +259 -0
- package/src/PackageActionsSection.jsx +52 -0
- package/src/PackageHero.jsx +97 -0
- package/src/PackageHowToSection.jsx +99 -0
- package/src/PackageServiceToggle.jsx +69 -0
- package/src/PackagesBrowser.jsx +294 -0
- package/src/WidgetCapabilityCard.jsx +28 -0
- package/src/en.translations.json +85 -0
- package/src/formatPaths.js +69 -0
- package/src/index.js +9 -0
- package/src/package-detail.page.jsx +197 -0
- package/src/packageCopy.js +194 -0
- package/src/packageDetailSections.js +17 -0
- package/src/packageStoreStyles.js +305 -0
- package/src/packages.page.jsx +15 -0
- package/src/sv.translations.json +85 -0
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ossy/package-catalog",
|
|
3
|
+
"description": "Capability catalog UI — browse manifest packages, actions, and integration examples",
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"module": "./src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"ossy": {
|
|
14
|
+
"src": "./src"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"/src",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@ossy/app": ">=1.0.0",
|
|
26
|
+
"@ossy/design-system": ">=1.0.0",
|
|
27
|
+
"@ossy/router-react": ">=1.0.0",
|
|
28
|
+
"@ossy/sdk-react": ">=1.0.0",
|
|
29
|
+
"@ossy/workspaces": ">=1.0.0",
|
|
30
|
+
"react": ">=19.0.0 <20.0.0"
|
|
31
|
+
},
|
|
32
|
+
"gitHead": "a0d89185a17f8de8ce328c3a648c108ff1d61d8f"
|
|
33
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, View } from '@ossy/design-system'
|
|
3
|
+
import { humanizeCapabilityId } from './packageCopy.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Widget-scale action card — clickable list item in the Actions split view.
|
|
7
|
+
*
|
|
8
|
+
* @param {{
|
|
9
|
+
* action: { id: string, access?: string, title?: string, description?: string, mcpTool?: string, inputSchema?: object }
|
|
10
|
+
* selected?: boolean
|
|
11
|
+
* onSelect?: () => void
|
|
12
|
+
* }} props
|
|
13
|
+
*/
|
|
14
|
+
export function ActionCapabilityCard ({ action, selected = false, onSelect }) {
|
|
15
|
+
const title = action.title || humanizeCapabilityId(action.id)
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<View
|
|
19
|
+
as="button"
|
|
20
|
+
type="button"
|
|
21
|
+
gap="s"
|
|
22
|
+
inset="m"
|
|
23
|
+
roundness="m"
|
|
24
|
+
surface="primary"
|
|
25
|
+
className="pkg-store-action-card"
|
|
26
|
+
onClick={onSelect}
|
|
27
|
+
aria-pressed={selected}
|
|
28
|
+
>
|
|
29
|
+
<View gap="2px">
|
|
30
|
+
<Text className="pkg-store-text-strong">{title}</Text>
|
|
31
|
+
<Text className="pkg-store-mono pkg-store-mono--sm pkg-store-text-subtle">{action.id}</Text>
|
|
32
|
+
</View>
|
|
33
|
+
|
|
34
|
+
{action.description && (
|
|
35
|
+
<Text variant="small" className="pkg-store-text-dim">
|
|
36
|
+
{action.description}
|
|
37
|
+
</Text>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
{action.access && (
|
|
41
|
+
<Text variant="small" className="pkg-store-text-subtle">
|
|
42
|
+
access: {action.access}
|
|
43
|
+
</Text>
|
|
44
|
+
)}
|
|
45
|
+
</View>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Merge manifest action with capability metadata from build-time capabilities.json.
|
|
51
|
+
*
|
|
52
|
+
* @param {{ id: string, access?: string }} action
|
|
53
|
+
* @param {Record<string, object> | undefined} schemaById keyed by action id
|
|
54
|
+
*/
|
|
55
|
+
export function enrichActionWithSchema (action, schemaById) {
|
|
56
|
+
const schema = schemaById?.[action.id]
|
|
57
|
+
if (!schema) return action
|
|
58
|
+
return {
|
|
59
|
+
...action,
|
|
60
|
+
title: schema.title,
|
|
61
|
+
description: schema.description,
|
|
62
|
+
mcpTool: schema.mcpTool,
|
|
63
|
+
inputSchema: schema.inputSchema,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {Array<{ actionId: string, name?: string, title?: string, description?: string, inputSchema?: object }>} tools */
|
|
68
|
+
export function buildCapabilitySchemaMap (tools) {
|
|
69
|
+
/** @type {Record<string, object>} */
|
|
70
|
+
const map = {}
|
|
71
|
+
for (const tool of tools || []) {
|
|
72
|
+
if (!tool?.actionId) continue
|
|
73
|
+
map[tool.actionId] = {
|
|
74
|
+
id: tool.actionId,
|
|
75
|
+
title: tool.title,
|
|
76
|
+
description: tool.description,
|
|
77
|
+
mcpTool: tool.name,
|
|
78
|
+
inputSchema: tool.inputSchema,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return map
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @param {{ tools?: Array<object> } | undefined} capabilities */
|
|
85
|
+
export function capabilitySchemaMapFromApp (capabilities) {
|
|
86
|
+
return buildCapabilitySchemaMap(capabilities?.tools)
|
|
87
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { HttpInvokeExample, SdkInvokeExample, Text, View } from '@ossy/design-system'
|
|
3
|
+
import { useApp, resolveEndpoints } from '@ossy/app/shell'
|
|
4
|
+
import { humanizeCapabilityId } from './packageCopy.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Split-view detail panel for a single action's invoke examples.
|
|
8
|
+
*
|
|
9
|
+
* @param {{ action: { id: string, title?: string, description?: string, mcpTool?: string, access?: string } }} props
|
|
10
|
+
*/
|
|
11
|
+
export function ActionExamplesPanel ({ action }) {
|
|
12
|
+
const app = useApp()
|
|
13
|
+
const { actionsUrl } = resolveEndpoints(app)
|
|
14
|
+
const title = action.title || humanizeCapabilityId(action.id)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<View
|
|
18
|
+
gap="s"
|
|
19
|
+
inset="m"
|
|
20
|
+
roundness="m"
|
|
21
|
+
surface="primary"
|
|
22
|
+
className="pkg-store-action-examples"
|
|
23
|
+
>
|
|
24
|
+
<View gap="2px">
|
|
25
|
+
<Text className="pkg-store-text-strong">{title}</Text>
|
|
26
|
+
<Text className="pkg-store-mono pkg-store-mono--sm pkg-store-text-subtle">{action.id}</Text>
|
|
27
|
+
</View>
|
|
28
|
+
|
|
29
|
+
{action.description && (
|
|
30
|
+
<Text variant="small" className="pkg-store-prose--muted">
|
|
31
|
+
{action.description}
|
|
32
|
+
</Text>
|
|
33
|
+
)}
|
|
34
|
+
|
|
35
|
+
{action.mcpTool && (
|
|
36
|
+
<Text variant="small" className="pkg-store-mono pkg-store-mono--xs pkg-store-text-muted">
|
|
37
|
+
MCP: {action.mcpTool}
|
|
38
|
+
</Text>
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
<View gap="xs">
|
|
42
|
+
<SdkInvokeExample actionId={action.id} />
|
|
43
|
+
<HttpInvokeExample actionId={action.id} actionsUrl={actionsUrl} />
|
|
44
|
+
</View>
|
|
45
|
+
</View>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { CodeExampleStyles } from '@ossy/design-system'
|
|
3
|
+
import { packageStoreStyles } from './packageStoreStyles.js'
|
|
4
|
+
|
|
5
|
+
/** Shared styles for catalog list and detail pages. */
|
|
6
|
+
export function CatalogPageStyles () {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<CodeExampleStyles />
|
|
10
|
+
<style href="@ossy/package-catalog/store" precedence="high">
|
|
11
|
+
{packageStoreStyles}
|
|
12
|
+
</style>
|
|
13
|
+
</>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button, Icon, Text, View } from '@ossy/design-system'
|
|
3
|
+
import { useRouter } from '@ossy/router-react'
|
|
4
|
+
import { scrollToPackageSection } from './packageDetailSections.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {{
|
|
8
|
+
* icon: string
|
|
9
|
+
* title: string
|
|
10
|
+
* body: string | React.ReactNode
|
|
11
|
+
* href?: string
|
|
12
|
+
* sectionId?: string
|
|
13
|
+
* bodyParams?: Record<string, unknown>
|
|
14
|
+
* }} props
|
|
15
|
+
*/
|
|
16
|
+
export function HowToCard ({ icon, title, body, href, sectionId, bodyParams }) {
|
|
17
|
+
const router = useRouter()
|
|
18
|
+
const isSectionLink = Boolean(sectionId)
|
|
19
|
+
|
|
20
|
+
const handleActivate = () => {
|
|
21
|
+
if (sectionId) scrollToPackageSection(sectionId)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const handleKeyDown = (event) => {
|
|
25
|
+
if (!sectionId) return
|
|
26
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
27
|
+
event.preventDefault()
|
|
28
|
+
scrollToPackageSection(sectionId)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cardClassName = [
|
|
33
|
+
'pkg-store-howto-card',
|
|
34
|
+
'pkg-store-envelope',
|
|
35
|
+
isSectionLink ? 'pkg-store-howto-card--link' : '',
|
|
36
|
+
].filter(Boolean).join(' ')
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View
|
|
40
|
+
gap="s"
|
|
41
|
+
inset="m"
|
|
42
|
+
roundness="m"
|
|
43
|
+
surface="primary"
|
|
44
|
+
className={cardClassName}
|
|
45
|
+
role={isSectionLink ? 'link' : undefined}
|
|
46
|
+
tabIndex={isSectionLink ? 0 : undefined}
|
|
47
|
+
onClick={isSectionLink ? handleActivate : undefined}
|
|
48
|
+
onKeyDown={isSectionLink ? handleKeyDown : undefined}
|
|
49
|
+
>
|
|
50
|
+
<View justifyContent="center" alignItems="center">
|
|
51
|
+
<Icon name={icon} size="m" />
|
|
52
|
+
</View>
|
|
53
|
+
<Text variant="small" className="pkg-store-text-bold pkg-store-text-center" text={title} />
|
|
54
|
+
{typeof body === 'string'
|
|
55
|
+
? (
|
|
56
|
+
<Text variant="small" className="pkg-store-card-body pkg-store-text-center pkg-store-prose--muted" text={body} params={bodyParams} />
|
|
57
|
+
)
|
|
58
|
+
: (
|
|
59
|
+
<View className="pkg-store-card-body pkg-store-text-center pkg-store-prose--muted">
|
|
60
|
+
{body}
|
|
61
|
+
</View>
|
|
62
|
+
)}
|
|
63
|
+
{href && (
|
|
64
|
+
<Button
|
|
65
|
+
variant="link"
|
|
66
|
+
href={router.getHref(href)}
|
|
67
|
+
label="package-catalog.home.howTo.http.createLink"
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
{sectionId && (
|
|
71
|
+
<Text
|
|
72
|
+
variant="small"
|
|
73
|
+
text="package-catalog.detail.howTo.sectionLink"
|
|
74
|
+
className="pkg-store-section-link"
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
</View>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button, Icon, Text, View, useLocale } from '@ossy/design-system'
|
|
3
|
+
import { useRouter } from '@ossy/router-react'
|
|
4
|
+
import { formatPagePathForLanguage, formatPagePaths, monoStyle } from './formatPaths.js'
|
|
5
|
+
import { getTaskDisplayDescription, getTaskDisplayTitle, humanizeCapabilityId } from './packageCopy.js'
|
|
6
|
+
import { pkgStoreEnvelopedSurfaceProps } from './packageStoreStyles.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {'pages' | 'actions' | 'entities' | 'secondary' | 'backgroundTasks' | 'list'} ManifestSectionVariant
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {{
|
|
14
|
+
* title: string
|
|
15
|
+
* subtitle?: string
|
|
16
|
+
* items: Array<{ id: string, path?: string | Record<string, string>, access?: string, title?: string, credentials?: string[], description?: string }>
|
|
17
|
+
* variant?: ManifestSectionVariant
|
|
18
|
+
* linkPages?: boolean
|
|
19
|
+
* icon?: string
|
|
20
|
+
* openPageLabel?: string
|
|
21
|
+
* accessLabel?: string
|
|
22
|
+
* credentialsLabel?: string
|
|
23
|
+
* titleVariant?: string
|
|
24
|
+
* compact?: boolean
|
|
25
|
+
* enveloped?: boolean
|
|
26
|
+
* sectionId?: string
|
|
27
|
+
* }} props
|
|
28
|
+
*/
|
|
29
|
+
export function ManifestSection ({
|
|
30
|
+
title,
|
|
31
|
+
subtitle,
|
|
32
|
+
items = [],
|
|
33
|
+
variant = 'list',
|
|
34
|
+
linkPages = false,
|
|
35
|
+
icon,
|
|
36
|
+
openPageLabel = 'package-catalog.detail.openPage',
|
|
37
|
+
accessLabel = 'design-system.dev.pagesPanel.accessLabel',
|
|
38
|
+
credentialsLabel = 'design-system.dev.pagesPanel.credentialsLabel',
|
|
39
|
+
titleVariant = 'heading-secondary',
|
|
40
|
+
compact = false,
|
|
41
|
+
enveloped = false,
|
|
42
|
+
sectionId,
|
|
43
|
+
}) {
|
|
44
|
+
const router = useRouter()
|
|
45
|
+
const { t } = useLocale()
|
|
46
|
+
|
|
47
|
+
if (!items.length) return null
|
|
48
|
+
|
|
49
|
+
const sectionIcon = icon || variantIcon(variant)
|
|
50
|
+
const resolvedTitleVariant = enveloped ? 'heading-secondary' : titleVariant
|
|
51
|
+
const subtitleOpacity = enveloped ? 0.75 : (compact ? 0.65 : 0.75)
|
|
52
|
+
|
|
53
|
+
const section = (
|
|
54
|
+
<View id={sectionId} gap={enveloped ? 'm' : (compact ? 's' : 'm')} className="pkg-store-section">
|
|
55
|
+
<View gap="xs">
|
|
56
|
+
<View layout="row" gap="s" alignItems="center">
|
|
57
|
+
{sectionIcon && (
|
|
58
|
+
enveloped
|
|
59
|
+
? <Icon name={sectionIcon} size="m" />
|
|
60
|
+
: (
|
|
61
|
+
<View
|
|
62
|
+
alignItems="center"
|
|
63
|
+
justifyContent="center"
|
|
64
|
+
style={{
|
|
65
|
+
width: compact ? 28 : 36,
|
|
66
|
+
height: compact ? 28 : 36,
|
|
67
|
+
borderRadius: compact ? 8 : 10,
|
|
68
|
+
background: 'color-mix(in srgb, var(--foreground) 6%, transparent)',
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Icon name={sectionIcon} size={compact ? 'xs' : 's'} />
|
|
72
|
+
</View>
|
|
73
|
+
)
|
|
74
|
+
)}
|
|
75
|
+
<Text as="h2" variant={resolvedTitleVariant} text={title} />
|
|
76
|
+
</View>
|
|
77
|
+
{subtitle && (
|
|
78
|
+
<Text text={subtitle} style={{ opacity: subtitleOpacity, maxWidth: 560, fontSize: enveloped ? undefined : (compact ? '0.875rem' : undefined) }} />
|
|
79
|
+
)}
|
|
80
|
+
</View>
|
|
81
|
+
|
|
82
|
+
{variant === 'pages' && (
|
|
83
|
+
<View gap="s" className="pkg-store-pages-list">
|
|
84
|
+
{items.map((item) => (
|
|
85
|
+
<View
|
|
86
|
+
key={item.id}
|
|
87
|
+
layout="row"
|
|
88
|
+
gap="m"
|
|
89
|
+
alignItems="center"
|
|
90
|
+
inset="m"
|
|
91
|
+
roundness="m"
|
|
92
|
+
surface="primary"
|
|
93
|
+
className="pkg-store-page-row"
|
|
94
|
+
style={{ border: '1px solid var(--separator-primary)' }}
|
|
95
|
+
>
|
|
96
|
+
<View gap="2px" style={{ flex: 1, minWidth: 0 }}>
|
|
97
|
+
<Text style={{ fontWeight: 600 }}>{item.title || humanizeCapabilityId(item.id)}</Text>
|
|
98
|
+
{item.path != null && (
|
|
99
|
+
<Text style={{ ...monoStyle, opacity: 0.8, fontSize: '0.75rem' }}>
|
|
100
|
+
{formatPagePathForLanguage(item.path, router.language)}
|
|
101
|
+
</Text>
|
|
102
|
+
)}
|
|
103
|
+
</View>
|
|
104
|
+
{linkPages && (
|
|
105
|
+
<Button
|
|
106
|
+
variant="secondary"
|
|
107
|
+
size="s"
|
|
108
|
+
href={router.getHref(item.id)}
|
|
109
|
+
icon="external"
|
|
110
|
+
title={t(openPageLabel)}
|
|
111
|
+
aria-label={t(openPageLabel)}
|
|
112
|
+
style={{ flexShrink: 0 }}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
</View>
|
|
116
|
+
))}
|
|
117
|
+
</View>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{variant === 'actions' && (
|
|
121
|
+
<View as="ul" gap="s" className="pkg-store-bullets" style={{ listStyle: 'none', padding: 0, margin: 0 }}>
|
|
122
|
+
{items.map((item) => (
|
|
123
|
+
<View as="li" key={item.id} layout="row" gap="s" alignItems="flex-start">
|
|
124
|
+
<Text style={{ opacity: 0.5, lineHeight: 1.5 }} aria-hidden>
|
|
125
|
+
•
|
|
126
|
+
</Text>
|
|
127
|
+
<View gap="2px" style={{ flex: 1 }}>
|
|
128
|
+
<Text style={{ fontWeight: 600 }}>{humanizeCapabilityId(item.id)}</Text>
|
|
129
|
+
<Text style={{ ...monoStyle, opacity: 0.65, fontSize: '0.75rem' }}>{item.id}</Text>
|
|
130
|
+
{item.access && (
|
|
131
|
+
<Text variant="small" text={accessLabel} params={{ access: item.access }} style={{ opacity: 0.7 }} />
|
|
132
|
+
)}
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
))}
|
|
136
|
+
</View>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{variant === 'backgroundTasks' && (
|
|
140
|
+
<View gap="s" className="pkg-store-secondary-grid">
|
|
141
|
+
{items.map((item) => {
|
|
142
|
+
const taskTitle = getTaskDisplayTitle(item)
|
|
143
|
+
const taskDescription = getTaskDisplayDescription(item)
|
|
144
|
+
return (
|
|
145
|
+
<View key={item.id} gap="xs" inset="m" roundness="m" className="pkg-store-secondary-item">
|
|
146
|
+
<Text style={{ fontWeight: 600, fontSize: '0.9375rem' }}>{taskTitle}</Text>
|
|
147
|
+
{taskDescription && (
|
|
148
|
+
<Text variant="small" style={{ opacity: 0.8 }}>
|
|
149
|
+
{taskDescription}
|
|
150
|
+
</Text>
|
|
151
|
+
)}
|
|
152
|
+
<Text style={{ ...monoStyle, opacity: 0.65, fontSize: '0.75rem' }}>{item.id}</Text>
|
|
153
|
+
</View>
|
|
154
|
+
)
|
|
155
|
+
})}
|
|
156
|
+
</View>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{variant === 'entities' && (
|
|
160
|
+
<View gap="s" className="pkg-store-entities-grid">
|
|
161
|
+
{items.map((item) => (
|
|
162
|
+
<View key={item.id} gap="xs" inset="m" roundness="m" surface="primary" className="pkg-store-entity">
|
|
163
|
+
<Text style={{ fontWeight: 600 }}>{item.title || humanizeCapabilityId(item.id)}</Text>
|
|
164
|
+
<Text style={{ ...monoStyle, opacity: 0.7, fontSize: '0.75rem' }}>{item.id}</Text>
|
|
165
|
+
{item.path != null && (
|
|
166
|
+
<Text style={{ ...monoStyle, opacity: 0.75, fontSize: '0.75rem' }}>
|
|
167
|
+
{formatPagePaths(item.path)}
|
|
168
|
+
</Text>
|
|
169
|
+
)}
|
|
170
|
+
{item.credentials?.length > 0 && (
|
|
171
|
+
<Text
|
|
172
|
+
variant="small"
|
|
173
|
+
text={credentialsLabel}
|
|
174
|
+
params={{ credentials: item.credentials.join(', ') }}
|
|
175
|
+
style={{ opacity: 0.7 }}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</View>
|
|
179
|
+
))}
|
|
180
|
+
</View>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{variant === 'secondary' && (
|
|
184
|
+
<View gap="xs" className="pkg-store-secondary-grid">
|
|
185
|
+
{items.map((item) => (
|
|
186
|
+
<View key={item.id} gap="2px" inset="s" roundness="s" className="pkg-store-secondary-item">
|
|
187
|
+
<Text style={{ fontWeight: 600, fontSize: '0.9375rem' }}>
|
|
188
|
+
{item.title || humanizeCapabilityId(item.id)}
|
|
189
|
+
</Text>
|
|
190
|
+
<Text style={{ ...monoStyle, opacity: 0.7, fontSize: '0.75rem' }}>{item.id}</Text>
|
|
191
|
+
{item.path != null && (
|
|
192
|
+
<Text style={{ ...monoStyle, opacity: 0.75, fontSize: '0.75rem' }}>
|
|
193
|
+
{formatPagePaths(item.path)}
|
|
194
|
+
</Text>
|
|
195
|
+
)}
|
|
196
|
+
{item.access && (
|
|
197
|
+
<Text variant="small" style={{ opacity: 0.7 }}>
|
|
198
|
+
{item.access}
|
|
199
|
+
</Text>
|
|
200
|
+
)}
|
|
201
|
+
</View>
|
|
202
|
+
))}
|
|
203
|
+
</View>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{variant === 'list' && (
|
|
207
|
+
<View gap="xs">
|
|
208
|
+
{items.map((item) => (
|
|
209
|
+
<View key={item.id} gap="2px" inset="s" roundness="s" surface="primary" className="pkg-store-card">
|
|
210
|
+
{linkPages ? (
|
|
211
|
+
<Button
|
|
212
|
+
variant="link"
|
|
213
|
+
href={router.getHref(item.id)}
|
|
214
|
+
style={{ justifyContent: 'flex-start', height: 'auto', padding: 0 }}
|
|
215
|
+
>
|
|
216
|
+
<Text style={{ ...monoStyle, fontWeight: 600 }}>{item.id}</Text>
|
|
217
|
+
</Button>
|
|
218
|
+
) : (
|
|
219
|
+
<Text style={{ ...monoStyle, fontWeight: 600 }}>{item.id}</Text>
|
|
220
|
+
)}
|
|
221
|
+
{item.path != null && (
|
|
222
|
+
<Text style={{ ...monoStyle, opacity: 0.85 }}>{formatPagePaths(item.path)}</Text>
|
|
223
|
+
)}
|
|
224
|
+
{item.title && <Text style={{ opacity: 0.8 }}>{item.title}</Text>}
|
|
225
|
+
{item.access && <Text style={{ opacity: 0.75 }}>access: {item.access}</Text>}
|
|
226
|
+
{item.credentials?.length > 0 && (
|
|
227
|
+
<Text style={{ opacity: 0.75 }}>credentials: {item.credentials.join(', ')}</Text>
|
|
228
|
+
)}
|
|
229
|
+
</View>
|
|
230
|
+
))}
|
|
231
|
+
</View>
|
|
232
|
+
)}
|
|
233
|
+
</View>
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if (enveloped) {
|
|
237
|
+
return <View {...pkgStoreEnvelopedSurfaceProps}>{section}</View>
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return section
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** @param {ManifestSectionVariant} variant */
|
|
244
|
+
function variantIcon (variant) {
|
|
245
|
+
switch (variant) {
|
|
246
|
+
case 'pages':
|
|
247
|
+
return 'file-document'
|
|
248
|
+
case 'actions':
|
|
249
|
+
return 'bolt'
|
|
250
|
+
case 'backgroundTasks':
|
|
251
|
+
return 'timer'
|
|
252
|
+
case 'entities':
|
|
253
|
+
return 'database'
|
|
254
|
+
case 'secondary':
|
|
255
|
+
return 'layout-grid'
|
|
256
|
+
default:
|
|
257
|
+
return 'list'
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Icon, Text, View } from '@ossy/design-system'
|
|
3
|
+
import { ActionCapabilityCard } from './ActionCapabilityCard.jsx'
|
|
4
|
+
import { ActionExamplesPanel } from './ActionExamplesPanel.jsx'
|
|
5
|
+
import { pkgStoreEnvelopedSurfaceProps } from './packageStoreStyles.js'
|
|
6
|
+
import { PKG_DETAIL_SECTION_IDS } from './packageDetailSections.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Actions section with enveloped surface and split-view examples panel.
|
|
10
|
+
*
|
|
11
|
+
* @param {{ actions: Array<{ id: string, access?: string, title?: string, description?: string, mcpTool?: string }> }} props
|
|
12
|
+
*/
|
|
13
|
+
export function PackageActionsSection ({ actions }) {
|
|
14
|
+
const [selectedId, setSelectedId] = useState(actions[0]?.id)
|
|
15
|
+
const selectedAction = actions.find((a) => a.id === selectedId) ?? actions[0]
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<View id={PKG_DETAIL_SECTION_IDS.actions} {...pkgStoreEnvelopedSurfaceProps}>
|
|
19
|
+
<View gap="xs">
|
|
20
|
+
<View layout="row" gap="s" alignItems="center">
|
|
21
|
+
<Icon name="bolt" size="m" />
|
|
22
|
+
<Text as="h2" variant="heading-secondary" text="package-catalog.detail.section.actions.title" />
|
|
23
|
+
</View>
|
|
24
|
+
<Text
|
|
25
|
+
text="package-catalog.detail.section.actions.subtitle"
|
|
26
|
+
className="pkg-store-prose--narrow"
|
|
27
|
+
/>
|
|
28
|
+
</View>
|
|
29
|
+
|
|
30
|
+
<View
|
|
31
|
+
gap="m"
|
|
32
|
+
className={selectedAction ? 'pkg-store-actions-split' : undefined}
|
|
33
|
+
layout={selectedAction ? undefined : 'row-wrap'}
|
|
34
|
+
>
|
|
35
|
+
<View gap="m" layout="row-wrap" className="pkg-store-actions-list">
|
|
36
|
+
{actions.map((action) => (
|
|
37
|
+
<ActionCapabilityCard
|
|
38
|
+
key={action.id}
|
|
39
|
+
action={action}
|
|
40
|
+
selected={action.id === selectedId}
|
|
41
|
+
onSelect={() => setSelectedId(action.id)}
|
|
42
|
+
/>
|
|
43
|
+
))}
|
|
44
|
+
</View>
|
|
45
|
+
|
|
46
|
+
{selectedAction && (
|
|
47
|
+
<ActionExamplesPanel action={selectedAction} />
|
|
48
|
+
)}
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button, Text, View, PageSection } from '@ossy/design-system'
|
|
3
|
+
|
|
4
|
+
const Cover = ({
|
|
5
|
+
title,
|
|
6
|
+
titleMaxWidth = '1100px',
|
|
7
|
+
text,
|
|
8
|
+
textParams,
|
|
9
|
+
eyebrow,
|
|
10
|
+
meta,
|
|
11
|
+
metaParams,
|
|
12
|
+
actions = [],
|
|
13
|
+
}) => (
|
|
14
|
+
<View
|
|
15
|
+
gap="l"
|
|
16
|
+
placeContent="center"
|
|
17
|
+
layout="column"
|
|
18
|
+
justifyContent="center"
|
|
19
|
+
className="pkg-store-hero-cover"
|
|
20
|
+
>
|
|
21
|
+
<View gap="m" alignItems="center">
|
|
22
|
+
{eyebrow && (
|
|
23
|
+
<Text
|
|
24
|
+
variant="small"
|
|
25
|
+
className="pkg-store-mono pkg-store-hero-eyebrow"
|
|
26
|
+
>
|
|
27
|
+
{eyebrow}
|
|
28
|
+
</Text>
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
<Text
|
|
32
|
+
as="h1"
|
|
33
|
+
variant="display"
|
|
34
|
+
text={title}
|
|
35
|
+
className="pkg-store-hero-title"
|
|
36
|
+
style={{ maxWidth: titleMaxWidth }}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
{text && (
|
|
40
|
+
<Text as="h2" variant="heading-tertiary" text={text} params={textParams} className="pkg-store-hero-subtitle" />
|
|
41
|
+
)}
|
|
42
|
+
|
|
43
|
+
{meta && (
|
|
44
|
+
<Text variant="small" text={meta} params={metaParams} className="pkg-store-hero-meta" />
|
|
45
|
+
)}
|
|
46
|
+
</View>
|
|
47
|
+
|
|
48
|
+
{actions.length > 0 && (
|
|
49
|
+
<View gap="m" layout="row" justifyContent="center" className="pkg-store-hero-actions">
|
|
50
|
+
{actions.map(({ label, ...props }) => (
|
|
51
|
+
<Button {...props} key={label} label={label} />
|
|
52
|
+
))}
|
|
53
|
+
</View>
|
|
54
|
+
)}
|
|
55
|
+
</View>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Marketing-style hero for package detail — mirrors @ossy/booking HeroCover.
|
|
60
|
+
*/
|
|
61
|
+
export const PackageHero = ({
|
|
62
|
+
title,
|
|
63
|
+
titleMaxWidth = '1100px',
|
|
64
|
+
text,
|
|
65
|
+
textParams,
|
|
66
|
+
eyebrow,
|
|
67
|
+
meta,
|
|
68
|
+
metaParams,
|
|
69
|
+
actions = [],
|
|
70
|
+
|
|
71
|
+
id = 'package-hero',
|
|
72
|
+
surfaceAs = 'div',
|
|
73
|
+
maxWidth = 'l',
|
|
74
|
+
surface = 'hero',
|
|
75
|
+
...props
|
|
76
|
+
}) => (
|
|
77
|
+
<PageSection
|
|
78
|
+
id={id}
|
|
79
|
+
surfaceAs={surfaceAs}
|
|
80
|
+
maxWidth={maxWidth}
|
|
81
|
+
surface={surface}
|
|
82
|
+
{...props}
|
|
83
|
+
data-rounded
|
|
84
|
+
data-component="@ossy/package-catalog/package-hero"
|
|
85
|
+
>
|
|
86
|
+
<Cover
|
|
87
|
+
title={title}
|
|
88
|
+
titleMaxWidth={titleMaxWidth}
|
|
89
|
+
text={text}
|
|
90
|
+
textParams={textParams}
|
|
91
|
+
eyebrow={eyebrow}
|
|
92
|
+
meta={meta}
|
|
93
|
+
metaParams={metaParams}
|
|
94
|
+
actions={actions}
|
|
95
|
+
/>
|
|
96
|
+
</PageSection>
|
|
97
|
+
)
|