@adobedjangir/commerce-admin-management 0.0.2

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.
Files changed (48) hide show
  1. package/README.md +637 -0
  2. package/actions/commerce-creds.js +262 -0
  3. package/actions/configurations/commerce/index.js +41 -0
  4. package/actions/configurations/commerce-connection-save/index.js +53 -0
  5. package/actions/configurations/commerce-connection-status/index.js +27 -0
  6. package/actions/configurations/commerce-connection-test/index.js +47 -0
  7. package/actions/configurations/export-config/index.js +256 -0
  8. package/actions/configurations/ext.config.yaml +192 -0
  9. package/actions/configurations/import-config/index.js +541 -0
  10. package/actions/configurations/registration/index.js +37 -0
  11. package/actions/configurations/sync-store-mappings-from-commerce/index.js +190 -0
  12. package/actions/configurations/system-config-list/index.js +127 -0
  13. package/actions/configurations/system-config-save/index.js +160 -0
  14. package/actions/configurations/system-config-schema/index.js +327 -0
  15. package/actions/utils.js +73 -0
  16. package/package.json +80 -0
  17. package/scripts/build-web.js +58 -0
  18. package/scripts/setup.js +445 -0
  19. package/src/abdb-config.js +8 -0
  20. package/src/abdb-helper.js +8 -0
  21. package/src/index.js +22 -0
  22. package/src/oauth1a.js +8 -0
  23. package/src/system-config-crypto.js +8 -0
  24. package/src/system-config-shared.js +8 -0
  25. package/web/dist/index.css +305 -0
  26. package/web/dist/index.js +2986 -0
  27. package/web/index.js +7 -0
  28. package/web/src/components/App.js +54 -0
  29. package/web/src/components/AppSectionNav.js +49 -0
  30. package/web/src/components/CommerceSetupWizard.js +160 -0
  31. package/web/src/components/ExtensionRegistration.js +33 -0
  32. package/web/src/components/MainPage.js +147 -0
  33. package/web/src/components/SystemConfig.js +1464 -0
  34. package/web/src/components/SystemConfigSchemaEditor.js +459 -0
  35. package/web/src/hooks/useConfirm.js +355 -0
  36. package/web/src/hooks/useSystemConfig.js +238 -0
  37. package/web/src/hooks/useSystemConfigSchema.js +102 -0
  38. package/web/src/index.js +46 -0
  39. package/web/src/nav-icons.js +30 -0
  40. package/web/src/nav.json +10 -0
  41. package/web/src/pages/index.js +17 -0
  42. package/web/src/schema/systemConfigSchema.js +82 -0
  43. package/web/src/settings.js +101 -0
  44. package/web/src/styles/index.css +337 -0
  45. package/web/src/theme.js +104 -0
  46. package/web/src/utils/storeMappingsFromCommerceRest.js +73 -0
  47. package/web/src/utils.js +52 -0
  48. package/web/styles.css +337 -0
@@ -0,0 +1,46 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ */
7
+
8
+ import './styles/index.css'
9
+
10
+ import App from './components/App'
11
+ import { MainPage } from './components/MainPage'
12
+ import ExtensionRegistration from './components/ExtensionRegistration'
13
+ import SystemConfig from './components/SystemConfig'
14
+ import SystemConfigSchemaEditor from './components/SystemConfigSchemaEditor'
15
+ import AppSectionNav from './components/AppSectionNav'
16
+
17
+ export { useSystemConfig } from './hooks/useSystemConfig'
18
+ export { useSystemConfigSchema } from './hooks/useSystemConfigSchema'
19
+ export { useConfirm } from './hooks/useConfirm'
20
+
21
+ export * from './schema/systemConfigSchema'
22
+ export { buildStoreMappingsFromCommercePayload } from './utils/storeMappingsFromCommerceRest'
23
+ export { callAction } from './utils'
24
+ export {
25
+ configureWeb,
26
+ getExtensionId,
27
+ getActionKey,
28
+ getNavItems,
29
+ getPageComponent,
30
+ DEFAULT_ACTION_KEYS
31
+ } from './settings'
32
+ export { NAV_ICONS, getNavIcon } from './nav-icons'
33
+ export { BUILT_IN_PAGES } from './pages'
34
+ export { THEME, PALETTE, RADIUS, SHADOW, SPACE, FONT } from './theme'
35
+
36
+ export {
37
+ App,
38
+ MainPage,
39
+ ExtensionRegistration,
40
+ SystemConfig,
41
+ SystemConfigSchemaEditor,
42
+ AppSectionNav
43
+ }
44
+
45
+ /** Full Commerce Admin extension shell (router + Spectrum provider). */
46
+ export { default as CommerceAdminManagementApp } from './components/App'
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ Licensed under the Apache License, Version 2.0
4
+ */
5
+
6
+ // Registry of Spectrum workflow icons that nav.json can reference by name.
7
+ // To use a new icon: import it here and add it to the map. nav.json then
8
+ // references it as `"icon": "<Name>"`.
9
+
10
+ import Settings from '@spectrum-icons/workflow/Settings'
11
+ import Properties from '@spectrum-icons/workflow/Properties'
12
+ import Data from '@spectrum-icons/workflow/Data'
13
+ import User from '@spectrum-icons/workflow/User'
14
+ import ShoppingCart from '@spectrum-icons/workflow/ShoppingCart'
15
+ import Box from '@spectrum-icons/workflow/Box'
16
+ import Folder from '@spectrum-icons/workflow/Folder'
17
+
18
+ export const NAV_ICONS = {
19
+ Settings,
20
+ Properties,
21
+ Data,
22
+ User,
23
+ ShoppingCart,
24
+ Box,
25
+ Folder
26
+ }
27
+
28
+ export function getNavIcon (name) {
29
+ return NAV_ICONS[name] || Settings
30
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "items": [
3
+ {
4
+ "id": "system-config",
5
+ "path": "/",
6
+ "label": "System Configurations",
7
+ "icon": "Settings"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ Licensed under the Apache License, Version 2.0
4
+ */
5
+
6
+ // Built-in page registry. Maps `nav.json` entry `id` → React component.
7
+ // To add a new built-in page:
8
+ // 1. Drop the component into ../components (or ./<file>.js here)
9
+ // 2. Import it and add it to BUILT_IN_PAGES below
10
+ // 3. Add a matching entry to ../nav.json
11
+ // Host apps add additional pages via `configureWeb({ extraPages })`.
12
+
13
+ import SystemConfig from '../components/SystemConfig'
14
+
15
+ export const BUILT_IN_PAGES = {
16
+ 'system-config': SystemConfig
17
+ }
@@ -0,0 +1,82 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ */
7
+
8
+ /**
9
+ * Schema is no longer defined statically — it is stored in App Builder DB
10
+ * and managed entirely through the Schema Designer UI. This module only
11
+ * exposes helpers for working with whatever schema shape is loaded at runtime.
12
+ *
13
+ * Schema shape (returned from the system-config-schema action):
14
+ * {
15
+ * sections: [{
16
+ * id, label,
17
+ * groups: [{
18
+ * id, label,
19
+ * fields: [{
20
+ * id, label, type, default,
21
+ * showIn: ['default' | 'websites' | 'stores'],
22
+ * sensitive?: boolean,
23
+ * options?: [{ value, label }] // type === 'select'
24
+ * }]
25
+ * }]
26
+ * }]
27
+ * }
28
+ */
29
+
30
+ export const FIELD_TYPES = ['text', 'textarea', 'password', 'number', 'select', 'boolean']
31
+ export const SCOPES = ['default', 'websites', 'stores']
32
+
33
+ const SENSITIVE_FIELD_TYPES = new Set(['password'])
34
+
35
+ export function emptySchema () {
36
+ return { sections: [] }
37
+ }
38
+
39
+ export function getFieldPath (sectionId, groupId, fieldId) {
40
+ return `${sectionId}/${groupId}/${fieldId}`
41
+ }
42
+
43
+ export function isFieldSensitive (field) {
44
+ return !!field?.sensitive || SENSITIVE_FIELD_TYPES.has(field?.type)
45
+ }
46
+
47
+ export function isFieldVisibleAtScope (field, scope) {
48
+ const allowed = field?.showIn || ['default']
49
+ return allowed.includes(scope)
50
+ }
51
+
52
+ export function flattenFields (schema) {
53
+ const out = []
54
+ if (!schema || !Array.isArray(schema.sections)) return out
55
+ for (const section of schema.sections) {
56
+ if (!Array.isArray(section.groups)) continue
57
+ for (const group of section.groups) {
58
+ if (!Array.isArray(group.fields)) continue
59
+ for (const field of group.fields) {
60
+ out.push({
61
+ section,
62
+ group,
63
+ field,
64
+ path: getFieldPath(section.id, group.id, field.id),
65
+ sensitive: isFieldSensitive(field)
66
+ })
67
+ }
68
+ }
69
+ }
70
+ return out
71
+ }
72
+
73
+ export function coerceDefault (field) {
74
+ switch (field?.type) {
75
+ case 'boolean':
76
+ return !!field.default
77
+ case 'number':
78
+ return typeof field.default === 'number' ? field.default : Number(field.default) || 0
79
+ default:
80
+ return field?.default ?? ''
81
+ }
82
+ }
@@ -0,0 +1,101 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ */
7
+
8
+ import builtinNav from './nav.json'
9
+ import { BUILT_IN_PAGES } from './pages'
10
+
11
+ /** Default OpenWhisk action keys (must match keys in deploy-time config.json). */
12
+ export const DEFAULT_ACTION_KEYS = {
13
+ commerceRestGet: 'CommerceAdminManagement/commerce-rest-get',
14
+ systemConfigList: 'CommerceAdminManagement/system-config-list',
15
+ systemConfigSave: 'CommerceAdminManagement/system-config-save',
16
+ systemConfigSchema: 'CommerceAdminManagement/system-config-schema',
17
+ exportConfig: 'CommerceAdminManagement/export-config',
18
+ importConfig: 'CommerceAdminManagement/import-config',
19
+ syncStoreMappings: 'CommerceAdminManagement/sync-store-mappings-from-commerce',
20
+ commerceConnectionStatus: 'CommerceAdminManagement/commerce-connection-status',
21
+ commerceConnectionTest: 'CommerceAdminManagement/commerce-connection-test',
22
+ commerceConnectionSave: 'CommerceAdminManagement/commerce-connection-save'
23
+ }
24
+
25
+ let extensionId = 'CommerceAdminManagement'
26
+ let actionUrls = {}
27
+ let actionKeys = { ...DEFAULT_ACTION_KEYS }
28
+
29
+ // Nav + page registries. Built-ins from this package merge with whatever the
30
+ // host app passes via configureWeb({ extraNav, extraPages }). Host entries win
31
+ // on id collisions so consumers can override built-in pages.
32
+ const builtinNavItems = Array.isArray(builtinNav && builtinNav.items) ? builtinNav.items : []
33
+ let extraNavItems = []
34
+ let extraPages = {}
35
+
36
+ export function getExtensionId () {
37
+ return extensionId
38
+ }
39
+
40
+ export function getActionKey (name) {
41
+ return actionKeys[name] || name
42
+ }
43
+
44
+ export function getActionUrl (actionKey) {
45
+ return actionUrls[actionKey]
46
+ }
47
+
48
+ /**
49
+ * Resolved nav list = built-in followed by host extras. Duplicate `id`s
50
+ * (host overriding built-in) keep the host's entry but the built-in slot.
51
+ */
52
+ export function getNavItems () {
53
+ const byId = new Map()
54
+ for (const it of builtinNavItems) byId.set(it.id, it)
55
+ for (const it of extraNavItems) byId.set(it.id, { ...byId.get(it.id), ...it })
56
+ return Array.from(byId.values())
57
+ }
58
+
59
+ /**
60
+ * Resolve a nav id to its React component. Host extras override built-ins.
61
+ */
62
+ export function getPageComponent (id) {
63
+ if (extraPages && extraPages[id]) return extraPages[id]
64
+ return BUILT_IN_PAGES[id] || null
65
+ }
66
+
67
+ /**
68
+ * Configure the web UI before rendering.
69
+ *
70
+ * @param {object} [options]
71
+ * @param {string} [options.extensionId]
72
+ * @param {Record<string, string>} [options.actionUrls]
73
+ * @param {Partial<typeof DEFAULT_ACTION_KEYS>} [options.actionKeys]
74
+ * @param {Array<{ id: string, path: string, label: string, icon?: string }>} [options.extraNav]
75
+ * Additional nav entries appended after the package's built-ins.
76
+ * @param {Record<string, import('react').ComponentType<any>>} [options.extraPages]
77
+ * Map of nav `id` → React component, registered from the host app.
78
+ */
79
+ export function configureWeb ({
80
+ extensionId: nextExtensionId,
81
+ actionUrls: nextActionUrls,
82
+ actionKeys: nextActionKeys,
83
+ extraNav: nextExtraNav,
84
+ extraPages: nextExtraPages
85
+ } = {}) {
86
+ if (nextExtensionId != null) {
87
+ extensionId = String(nextExtensionId)
88
+ }
89
+ if (nextActionUrls) {
90
+ actionUrls = { ...nextActionUrls }
91
+ }
92
+ if (nextActionKeys) {
93
+ actionKeys = { ...actionKeys, ...nextActionKeys }
94
+ }
95
+ if (Array.isArray(nextExtraNav)) {
96
+ extraNavItems = nextExtraNav.filter((it) => it && it.id && it.path)
97
+ }
98
+ if (nextExtraPages && typeof nextExtraPages === 'object') {
99
+ extraPages = { ...nextExtraPages }
100
+ }
101
+ }
@@ -0,0 +1,337 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ */
7
+
8
+ /* =========================================================================
9
+ sync-management — single source of truth for the UI theme.
10
+
11
+ To re-skin the app, change values under :root. Components consume these
12
+ tokens either directly via CSS classes (.sm-*) or via the JS facade in
13
+ `web-src/src/theme.js` which re-exports them as `var(--sm-*)` strings.
14
+
15
+ Token naming:
16
+ --sm-color-* colors
17
+ --sm-radius-* border radii
18
+ --sm-space-* spacing scale
19
+ --sm-shadow-* elevation
20
+ --sm-font-* typography
21
+ --sm-control-* interactive control sizing
22
+ ========================================================================= */
23
+
24
+ :root {
25
+ /* ---- Colors --------------------------------------------------------- */
26
+ --sm-color-bg: #f7f8fa;
27
+ --sm-color-surface: #ffffff;
28
+ --sm-color-surface-muted: #f3f4f6;
29
+ --sm-color-surface-subtle: #fafbfc;
30
+ --sm-color-border: #e5e7eb;
31
+ --sm-color-border-strong: #d1d5db;
32
+ --sm-color-text: #111827;
33
+ --sm-color-text-muted: #6b7280;
34
+ --sm-color-text-inverse: #ffffff;
35
+
36
+ --sm-color-accent: #1473e6;
37
+ --sm-color-accent-hover: #0f5fc4;
38
+ --sm-color-accent-soft: #e8f1fc;
39
+
40
+ --sm-color-success: #22863a;
41
+ --sm-color-success-hover: #1a6e2f;
42
+ --sm-color-success-soft: #ecfdf5;
43
+ --sm-color-warning: #b58105;
44
+ --sm-color-warning-hover: #946c04;
45
+ --sm-color-warning-soft: #fff7ed;
46
+ --sm-color-warning-border: #fde68a;
47
+ --sm-color-warning-text: #92400e;
48
+ --sm-color-warning-tint: #fef3c7;
49
+ --sm-color-danger: #c0392b;
50
+ --sm-color-danger-hover: #a32d20;
51
+ --sm-color-danger-soft: #fef2f2;
52
+ --sm-color-danger-tint: #fee2e2;
53
+ --sm-color-accent-tint: #dbeafe;
54
+
55
+ --sm-color-neutral-soft: #eef2f7;
56
+ --sm-color-neutral-text: #374151;
57
+
58
+ /* Surfaces used by the modal scaffolding (header text, body text, panel). */
59
+ --sm-color-text-strong: #1f2937;
60
+ --sm-color-text-soft: #475569;
61
+ --sm-color-surface-panel: #f9fafb;
62
+
63
+ /* Modal / overlay scrim */
64
+ --sm-color-overlay: rgba(15, 23, 42, 0.45);
65
+
66
+ /* ---- Radii ---------------------------------------------------------- */
67
+ --sm-radius-sm: 4px;
68
+ --sm-radius-md: 8px;
69
+ --sm-radius-lg: 10px;
70
+ --sm-radius-xl: 12px;
71
+ --sm-radius-2xl: 14px;
72
+ --sm-radius-pill: 999px;
73
+
74
+ /* ---- Spacing -------------------------------------------------------- */
75
+ --sm-space-1: 4px;
76
+ --sm-space-2: 8px;
77
+ --sm-space-3: 12px;
78
+ --sm-space-4: 16px;
79
+ --sm-space-5: 20px;
80
+ --sm-space-6: 24px;
81
+
82
+ /* ---- Shadows -------------------------------------------------------- */
83
+ --sm-shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.03);
84
+ --sm-shadow-sm: 0 1px 4px rgba(15, 23, 42, 0.04);
85
+ --sm-shadow-md: 0 1px 3px rgba(15, 23, 42, 0.12), 0 1px 1px rgba(15, 23, 42, 0.06);
86
+ --sm-shadow-pill: 0 1px 3px rgba(15, 23, 42, 0.10), 0 1px 1px rgba(15, 23, 42, 0.06);
87
+ --sm-shadow-floating: 0 4px 14px rgba(15, 23, 42, 0.08);
88
+ --sm-shadow-dropdown: 0 12px 28px rgba(15, 23, 42, 0.16), 0 2px 4px rgba(15, 23, 42, 0.06);
89
+ --sm-shadow-modal: 0 24px 60px rgba(15, 23, 42, 0.25), 0 2px 8px rgba(15, 23, 42, 0.08);
90
+ --sm-shadow-inset: inset 0 1px 2px rgba(15, 23, 42, 0.04);
91
+
92
+ /* ---- Typography ----------------------------------------------------- */
93
+ --sm-font-family: adobe-clean, 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
94
+ --sm-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
95
+ --sm-font-size-xs: 11px;
96
+ --sm-font-size-sm: 12px;
97
+ --sm-font-size-md: 13px;
98
+ --sm-font-size-lg: 14px;
99
+ --sm-font-weight-regular: 400;
100
+ --sm-font-weight-medium: 500;
101
+ --sm-font-weight-semibold:600;
102
+ --sm-font-weight-bold: 700;
103
+
104
+ /* ---- Control sizing ------------------------------------------------- */
105
+ --sm-control-height-sm: 28px;
106
+ --sm-control-height-md: 32px;
107
+ --sm-control-height-lg: 40px;
108
+ --sm-control-padding-x: 16px;
109
+
110
+ /* ---- Z-index -------------------------------------------------------- */
111
+ --sm-z-nav: 30;
112
+ --sm-z-sticky: 20;
113
+ --sm-z-modal: 100;
114
+
115
+ /* =====================================================================
116
+ Spectrum token overrides — re-skin React-Spectrum widgets without
117
+ forking the theme. Keep these in sync with the --sm-* tokens above
118
+ so default Buttons / TextField / Picker pick up our accent automatically.
119
+ ===================================================================== */
120
+ --spectrum-accent-color-900: var(--sm-color-accent);
121
+ --spectrum-accent-color-1000: var(--sm-color-accent-hover);
122
+ /* Text colours are left to Spectrum so secondary/quiet buttons keep
123
+ their proper contrast on both light and dark surfaces. */
124
+
125
+ color-scheme: light;
126
+ }
127
+
128
+ /* =========================================================================
129
+ Base
130
+ ========================================================================= */
131
+ html,
132
+ body,
133
+ #root {
134
+ margin: 0;
135
+ min-height: 100%;
136
+ background: var(--sm-color-bg);
137
+ color: var(--sm-color-text);
138
+ font-family: var(--sm-font-family);
139
+ }
140
+
141
+ /* React-Spectrum's <Provider> wraps its tree in a div. We mark it with
142
+ `UNSAFE_className="sm-provider"` and set its background here so the page
143
+ bg fills the whole iframe — but we only paint the wrapper, NOT any
144
+ descendants. Painting descendants stomps on inputs / buttons / pickers. */
145
+ .sm-provider {
146
+ background: var(--sm-color-bg);
147
+ min-height: 100vh;
148
+ }
149
+
150
+ /* =========================================================================
151
+ Cards
152
+ ========================================================================= */
153
+ .sm-card {
154
+ background: var(--sm-color-surface);
155
+ border: 1px solid var(--sm-color-border);
156
+ border-radius: var(--sm-radius-lg);
157
+ box-shadow: var(--sm-shadow-xs);
158
+ padding: var(--sm-space-5);
159
+ }
160
+ .sm-card--flush { padding: 0; }
161
+
162
+ /* =========================================================================
163
+ Pills
164
+ ========================================================================= */
165
+ .sm-pill {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ gap: var(--sm-space-1);
169
+ padding: 2px var(--sm-space-2);
170
+ border-radius: var(--sm-radius-pill);
171
+ background: var(--sm-color-neutral-soft);
172
+ color: var(--sm-color-neutral-text);
173
+ font-size: var(--sm-font-size-xs);
174
+ font-weight: var(--sm-font-weight-semibold);
175
+ line-height: 16px;
176
+ letter-spacing: 0.2px;
177
+ white-space: nowrap;
178
+ }
179
+ .sm-pill--accent { background: var(--sm-color-accent-soft); color: var(--sm-color-accent); }
180
+ .sm-pill--success { background: var(--sm-color-success-soft); color: var(--sm-color-success); }
181
+ .sm-pill--warning { background: var(--sm-color-warning-soft); color: var(--sm-color-warning); }
182
+ .sm-pill--danger { background: var(--sm-color-danger-soft); color: var(--sm-color-danger); }
183
+
184
+ /* =========================================================================
185
+ Tab bar (used by AppSectionNav)
186
+ ========================================================================= */
187
+ .sm-tab-bar {
188
+ position: sticky;
189
+ top: 0;
190
+ z-index: var(--sm-z-nav);
191
+ background: var(--sm-color-surface);
192
+ border-bottom: 1px solid var(--sm-color-border);
193
+ padding: 10px var(--sm-space-4);
194
+ box-shadow: var(--sm-shadow-sm);
195
+ box-sizing: border-box;
196
+ max-width: 100%;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ flex-wrap: nowrap;
201
+ gap: var(--sm-space-3);
202
+ }
203
+ .sm-tab-bar__actions {
204
+ display: inline-flex;
205
+ align-items: center;
206
+ gap: var(--sm-space-2);
207
+ flex-shrink: 0;
208
+ }
209
+ .sm-tab-bar__track {
210
+ display: inline-flex;
211
+ padding: var(--sm-space-1);
212
+ background: var(--sm-color-surface-muted);
213
+ border: 1px solid var(--sm-color-border);
214
+ border-radius: var(--sm-radius-pill);
215
+ box-shadow: var(--sm-shadow-inset);
216
+ gap: 2px;
217
+ font-family: var(--sm-font-family);
218
+ }
219
+ .sm-tab {
220
+ display: inline-flex;
221
+ align-items: center;
222
+ gap: var(--sm-space-2);
223
+ padding: var(--sm-space-2) var(--sm-control-padding-x);
224
+ border: 0;
225
+ border-radius: var(--sm-radius-pill);
226
+ background: transparent;
227
+ color: var(--sm-color-neutral-text);
228
+ font-size: var(--sm-font-size-md);
229
+ font-weight: var(--sm-font-weight-semibold);
230
+ letter-spacing: 0.1px;
231
+ cursor: pointer;
232
+ transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
233
+ }
234
+ .sm-tab:hover {
235
+ background: var(--sm-color-surface);
236
+ color: var(--sm-color-text);
237
+ }
238
+ .sm-tab.is-active {
239
+ background: var(--sm-color-surface);
240
+ color: var(--sm-color-accent);
241
+ font-weight: var(--sm-font-weight-bold);
242
+ box-shadow: var(--sm-shadow-pill);
243
+ cursor: default;
244
+ }
245
+ .sm-tab__icon { display: inline-flex; opacity: 0.75; }
246
+ .sm-tab.is-active .sm-tab__icon { opacity: 1; }
247
+
248
+ /* =========================================================================
249
+ Spectrum textareas inside the system-config field renderer
250
+ ========================================================================= */
251
+ .sm-textarea textarea {
252
+ height: 160px !important;
253
+ max-height: 160px !important;
254
+ min-height: 160px !important;
255
+ overflow-y: auto !important;
256
+ resize: none !important;
257
+ font-family: var(--sm-font-mono);
258
+ font-size: var(--sm-font-size-sm);
259
+ }
260
+
261
+ /* =========================================================================
262
+ Note banner (used by ComingSoon, info callouts, etc.)
263
+ ========================================================================= */
264
+ .sm-note {
265
+ margin-top: var(--sm-space-3);
266
+ padding: var(--sm-space-3);
267
+ border-radius: var(--sm-radius-md);
268
+ background: var(--sm-color-surface-muted);
269
+ border: 1px solid var(--sm-color-border);
270
+ color: var(--sm-color-text);
271
+ font-size: var(--sm-font-size-md);
272
+ white-space: pre-line;
273
+ font-family: var(--sm-font-mono);
274
+ }
275
+ .sm-note--warning {
276
+ background: var(--sm-color-warning-soft);
277
+ border-color: var(--sm-color-warning-border);
278
+ color: var(--sm-color-warning-text);
279
+ font-family: var(--sm-font-family);
280
+ }
281
+
282
+ /* =========================================================================
283
+ Animations (used by progress / status indicators)
284
+ ========================================================================= */
285
+ @keyframes sm-pulse {
286
+ 0%, 100% { opacity: 1; }
287
+ 50% { opacity: 0.3; }
288
+ }
289
+ @keyframes sm-indeterminate {
290
+ 0% { left: -40%; width: 40%; }
291
+ 50% { left: 30%; width: 40%; }
292
+ 100% { left: 100%; width: 40%; }
293
+ }
294
+ @keyframes sm-fade-in {
295
+ from { opacity: 0; }
296
+ to { opacity: 1; }
297
+ }
298
+ @keyframes sm-pop-in {
299
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
300
+ to { opacity: 1; transform: translateY(0) scale(1); }
301
+ }
302
+
303
+ /* =========================================================================
304
+ Legacy SideNav classes (kept for backwards compat with any remaining
305
+ menu markup; safe to delete once nothing references them).
306
+ ========================================================================= */
307
+ .SideNav {
308
+ list-style-type: none;
309
+ margin: 0;
310
+ padding: 0;
311
+ outline: none;
312
+ height: 100%;
313
+ }
314
+ .SideNav-item {
315
+ list-style-type: none;
316
+ margin: var(--spectrum-global-dimension-size-50) 0;
317
+ }
318
+ .SideNav-itemLink {
319
+ position: relative;
320
+ display: inline-flex;
321
+ align-items: center;
322
+ box-sizing: border-box;
323
+ width: 100%;
324
+ padding: var(--sm-space-2) var(--sm-space-3);
325
+ border-radius: var(--sm-radius-sm);
326
+ font-size: var(--sm-font-size-lg);
327
+ font-weight: var(--sm-font-weight-regular);
328
+ text-decoration: none;
329
+ word-break: break-word;
330
+ cursor: pointer;
331
+ background: transparent;
332
+ color: var(--sm-color-text);
333
+ }
334
+ .SideNav-itemLink.is-selected {
335
+ color: var(--sm-color-accent);
336
+ background: var(--sm-color-accent-soft);
337
+ }