@tai42/studio-sdk 25.0.0 → 26.0.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/README.md +143 -26
- package/dist/components/action-menu.d.ts +30 -0
- package/dist/components/action-menu.d.ts.map +1 -0
- package/dist/components/action-menu.js +101 -0
- package/dist/components/action-menu.js.map +1 -0
- package/dist/components/agent-picker.d.ts +22 -0
- package/dist/components/agent-picker.d.ts.map +1 -0
- package/dist/components/agent-picker.js +17 -0
- package/dist/components/agent-picker.js.map +1 -0
- package/dist/components/channel-picker.d.ts +27 -0
- package/dist/components/channel-picker.d.ts.map +1 -0
- package/dist/components/channel-picker.js +26 -0
- package/dist/components/channel-picker.js.map +1 -0
- package/dist/components/components.css +226 -8
- package/dist/components/confirm-dialog.d.ts +3 -1
- package/dist/components/confirm-dialog.d.ts.map +1 -1
- package/dist/components/confirm-dialog.js +2 -2
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/execution-key-picker.d.ts +26 -10
- package/dist/components/execution-key-picker.d.ts.map +1 -1
- package/dist/components/execution-key-picker.js +134 -15
- package/dist/components/execution-key-picker.js.map +1 -1
- package/dist/components/extension-combo-builder.d.ts +7 -7
- package/dist/components/extension-combo-builder.d.ts.map +1 -1
- package/dist/components/extension-combo-builder.js +42 -16
- package/dist/components/extension-combo-builder.js.map +1 -1
- package/dist/components/extension-combo-parts.d.ts +13 -11
- package/dist/components/extension-combo-parts.d.ts.map +1 -1
- package/dist/components/extension-combo-parts.js +10 -19
- package/dist/components/extension-combo-parts.js.map +1 -1
- package/dist/components/extension-config-editor.d.ts +33 -0
- package/dist/components/extension-config-editor.d.ts.map +1 -0
- package/dist/components/extension-config-editor.js +190 -0
- package/dist/components/extension-config-editor.js.map +1 -0
- package/dist/components/extension-grouping.d.ts +4 -41
- package/dist/components/extension-grouping.d.ts.map +1 -1
- package/dist/components/extension-grouping.js +1 -48
- package/dist/components/extension-grouping.js.map +1 -1
- package/dist/components/extension-picker.d.ts +4 -3
- package/dist/components/extension-picker.d.ts.map +1 -1
- package/dist/components/extension-picker.js +6 -6
- package/dist/components/extension-picker.js.map +1 -1
- package/dist/components/fleet-report.d.ts +8 -3
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +12 -2
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/holder-kinds.d.ts +18 -0
- package/dist/components/holder-kinds.d.ts.map +1 -0
- package/dist/components/holder-kinds.js +41 -0
- package/dist/components/holder-kinds.js.map +1 -0
- package/dist/components/holder-list.d.ts +58 -0
- package/dist/components/holder-list.d.ts.map +1 -0
- package/dist/components/holder-list.js +77 -0
- package/dist/components/holder-list.js.map +1 -0
- package/dist/components/icons/index.d.ts +1 -1
- package/dist/components/icons/index.d.ts.map +1 -1
- package/dist/components/icons/index.js +1 -1
- package/dist/components/icons/index.js.map +1 -1
- package/dist/components/icons/nav-map.d.ts.map +1 -1
- package/dist/components/icons/nav-map.js +1 -3
- package/dist/components/icons/nav-map.js.map +1 -1
- package/dist/components/icons/nav-marks.d.ts +0 -4
- package/dist/components/icons/nav-marks.d.ts.map +1 -1
- package/dist/components/icons/nav-marks.js +0 -4
- package/dist/components/icons/nav-marks.js.map +1 -1
- package/dist/components/named-list-picker.d.ts +39 -0
- package/dist/components/named-list-picker.d.ts.map +1 -0
- package/dist/components/named-list-picker.js +61 -0
- package/dist/components/named-list-picker.js.map +1 -0
- package/dist/components/named-list-test-utils.d.ts +23 -0
- package/dist/components/named-list-test-utils.d.ts.map +1 -0
- package/dist/components/named-list-test-utils.js +33 -0
- package/dist/components/named-list-test-utils.js.map +1 -0
- package/dist/components/recycle-roll.d.ts +23 -0
- package/dist/components/recycle-roll.d.ts.map +1 -0
- package/dist/components/recycle-roll.js +47 -0
- package/dist/components/recycle-roll.js.map +1 -0
- package/dist/components/stepper.d.ts +28 -0
- package/dist/components/stepper.d.ts.map +1 -0
- package/dist/components/stepper.js +33 -0
- package/dist/components/stepper.js.map +1 -0
- package/dist/components/subject-section.d.ts +2 -1
- package/dist/components/subject-section.d.ts.map +1 -1
- package/dist/components/subject-section.js +10 -5
- package/dist/components/subject-section.js.map +1 -1
- package/dist/components/tokens.css +31 -41
- package/dist/components/tokens.d.ts +3 -4
- package/dist/components/tokens.d.ts.map +1 -1
- package/dist/components/tokens.js +5 -13
- package/dist/components/tokens.js.map +1 -1
- package/dist/components/tool-picker-parts.d.ts +5 -1
- package/dist/components/tool-picker-parts.d.ts.map +1 -1
- package/dist/components/tool-picker-parts.js +8 -6
- package/dist/components/tool-picker-parts.js.map +1 -1
- package/dist/components/tool-picker.d.ts +77 -2
- package/dist/components/tool-picker.d.ts.map +1 -1
- package/dist/components/tool-picker.js +87 -29
- package/dist/components/tool-picker.js.map +1 -1
- package/dist/components/version-history-panel.d.ts +7 -1
- package/dist/components/version-history-panel.d.ts.map +1 -1
- package/dist/components/version-history-panel.js +2 -2
- package/dist/components/version-history-panel.js.map +1 -1
- package/dist/components/version-history-rows.d.ts +4 -2
- package/dist/components/version-history-rows.d.ts.map +1 -1
- package/dist/components/version-history-rows.js +14 -2
- package/dist/components/version-history-rows.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/hooks/useApi.d.ts +524 -197
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.d.ts +43 -10
- package/dist/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.js +39 -10
- package/dist/hooks/useCapabilities.js.map +1 -1
- package/dist/index.d.ts +25 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/marketplace-install/env-var-fields.d.ts +17 -0
- package/dist/marketplace-install/env-var-fields.d.ts.map +1 -0
- package/dist/marketplace-install/env-var-fields.js +22 -0
- package/dist/marketplace-install/env-var-fields.js.map +1 -0
- package/dist/marketplace-install/index.d.ts +18 -0
- package/dist/marketplace-install/index.d.ts.map +1 -0
- package/dist/marketplace-install/index.js +8 -0
- package/dist/marketplace-install/index.js.map +1 -0
- package/dist/marketplace-install/install-env.d.ts +21 -0
- package/dist/marketplace-install/install-env.d.ts.map +1 -0
- package/dist/marketplace-install/install-env.js +22 -0
- package/dist/marketplace-install/install-env.js.map +1 -0
- package/dist/marketplace-install/install-mount.d.ts +42 -0
- package/dist/marketplace-install/install-mount.d.ts.map +1 -0
- package/dist/marketplace-install/install-mount.js +62 -0
- package/dist/marketplace-install/install-mount.js.map +1 -0
- package/dist/marketplace-install/install-report.d.ts +14 -0
- package/dist/marketplace-install/install-report.d.ts.map +1 -0
- package/dist/marketplace-install/install-report.js +19 -0
- package/dist/marketplace-install/install-report.js.map +1 -0
- package/dist/marketplace-install/install-shell.d.ts +34 -0
- package/dist/marketplace-install/install-shell.d.ts.map +1 -0
- package/dist/marketplace-install/install-shell.js +32 -0
- package/dist/marketplace-install/install-shell.js.map +1 -0
- package/dist/marketplace-install/mount-install.d.ts +41 -0
- package/dist/marketplace-install/mount-install.d.ts.map +1 -0
- package/dist/marketplace-install/mount-install.js +90 -0
- package/dist/marketplace-install/mount-install.js.map +1 -0
- package/dist/marketplace-install/plugin-detail.d.ts +17 -0
- package/dist/marketplace-install/plugin-detail.d.ts.map +1 -0
- package/dist/marketplace-install/plugin-detail.js +41 -0
- package/dist/marketplace-install/plugin-detail.js.map +1 -0
- package/dist/marketplace-install/plugin-install.d.ts +27 -0
- package/dist/marketplace-install/plugin-install.d.ts.map +1 -0
- package/dist/marketplace-install/plugin-install.js +89 -0
- package/dist/marketplace-install/plugin-install.js.map +1 -0
- package/dist/marketplace-install/route-items.d.ts +14 -0
- package/dist/marketplace-install/route-items.d.ts.map +1 -0
- package/dist/marketplace-install/route-items.js +11 -0
- package/dist/marketplace-install/route-items.js.map +1 -0
- package/dist/marketplace-install/use-debounced-value.d.ts +6 -0
- package/dist/marketplace-install/use-debounced-value.d.ts.map +1 -0
- package/dist/marketplace-install/use-debounced-value.js +18 -0
- package/dist/marketplace-install/use-debounced-value.js.map +1 -0
- package/dist/marketplace-install/use-install-action.d.ts +26 -0
- package/dist/marketplace-install/use-install-action.d.ts.map +1 -0
- package/dist/marketplace-install/use-install-action.js +35 -0
- package/dist/marketplace-install/use-install-action.js.map +1 -0
- package/dist/marketplace-install/use-install-preview.d.ts +33 -0
- package/dist/marketplace-install/use-install-preview.d.ts.map +1 -0
- package/dist/marketplace-install/use-install-preview.js +44 -0
- package/dist/marketplace-install/use-install-preview.js.map +1 -0
- package/dist/marketplace-install/use-mount-install.d.ts +80 -0
- package/dist/marketplace-install/use-mount-install.d.ts.map +1 -0
- package/dist/marketplace-install/use-mount-install.js +75 -0
- package/dist/marketplace-install/use-mount-install.js.map +1 -0
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/types.d.ts +23 -7
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/plugin/registry.d.ts +0 -8
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +25 -1
- package/dist/plugin/registry.js.map +1 -1
- package/dist/plugin/types.d.ts +36 -12
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/version.d.ts +6 -4
- package/dist/plugin/version.d.ts.map +1 -1
- package/dist/plugin/version.js +6 -4
- package/dist/plugin/version.js.map +1 -1
- package/dist/query-keys.d.ts +45 -4
- package/dist/query-keys.d.ts.map +1 -1
- package/dist/query-keys.js +52 -4
- package/dist/query-keys.js.map +1 -1
- package/dist/schema-form/json-schema-meta.d.ts +3 -0
- package/dist/schema-form/json-schema-meta.d.ts.map +1 -0
- package/dist/schema-form/json-schema-meta.js +3 -0
- package/dist/schema-form/json-schema-meta.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -1
- package/dist/token-rules.d.ts +26 -0
- package/dist/token-rules.d.ts.map +1 -0
- package/dist/token-rules.js +64 -0
- package/dist/token-rules.js.map +1 -0
- package/package.json +7 -7
- package/src/components/action-menu.tsx +160 -0
- package/src/components/agent-picker.tsx +53 -0
- package/src/components/channel-picker.tsx +68 -0
- package/src/components/components.css +226 -8
- package/src/components/confirm-dialog.tsx +4 -1
- package/src/components/execution-key-picker.tsx +252 -36
- package/src/components/extension-combo-builder.tsx +61 -29
- package/src/components/extension-combo-parts.tsx +28 -38
- package/src/components/extension-config-editor.tsx +307 -0
- package/src/components/extension-grouping.ts +4 -73
- package/src/components/extension-picker.tsx +13 -13
- package/src/components/fleet-report.tsx +38 -4
- package/src/components/holder-kinds.ts +60 -0
- package/src/components/holder-list.tsx +163 -0
- package/src/components/icons/index.ts +0 -2
- package/src/components/icons/nav-map.ts +0 -4
- package/src/components/icons/nav-marks.tsx +0 -15
- package/src/components/named-list-picker.tsx +182 -0
- package/src/components/named-list-test-utils.tsx +47 -0
- package/src/components/recycle-roll.tsx +120 -0
- package/src/components/stepper.tsx +111 -0
- package/src/components/subject-section.tsx +10 -5
- package/src/components/tokens.css +31 -41
- package/src/components/tokens.ts +5 -13
- package/src/components/tool-picker-parts.tsx +17 -6
- package/src/components/tool-picker.tsx +215 -41
- package/src/components/version-history-panel.tsx +8 -0
- package/src/components/version-history-rows.tsx +14 -2
- package/src/errors.ts +10 -0
- package/src/hooks/useCapabilities.ts +59 -10
- package/src/index.ts +54 -14
- package/src/marketplace-install/env-var-fields.tsx +59 -0
- package/src/marketplace-install/index.ts +17 -0
- package/src/marketplace-install/install-env.ts +39 -0
- package/src/marketplace-install/install-mount.ts +96 -0
- package/src/marketplace-install/install-report.tsx +54 -0
- package/src/marketplace-install/install-shell.tsx +99 -0
- package/src/marketplace-install/mount-install.tsx +290 -0
- package/src/marketplace-install/plugin-detail.ts +60 -0
- package/src/marketplace-install/plugin-install.tsx +221 -0
- package/src/marketplace-install/route-items.ts +20 -0
- package/src/marketplace-install/use-debounced-value.ts +18 -0
- package/src/marketplace-install/use-install-action.ts +60 -0
- package/src/marketplace-install/use-install-preview.ts +74 -0
- package/src/marketplace-install/use-mount-install.ts +116 -0
- package/src/navigation/index.ts +1 -0
- package/src/navigation/types.ts +27 -7
- package/src/plugin/registry.ts +19 -1
- package/src/plugin/types.ts +40 -12
- package/src/plugin/version.ts +6 -4
- package/src/query-keys.ts +71 -4
- package/src/schema-form/json-schema-meta.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/token-rules.ts +66 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source rules over the design-token names, shared by the repo's token-usage gate and
|
|
3
|
+
* by any host that scans its own sources with the same rule (exported from
|
|
4
|
+
* `@tai42/studio-sdk/testing`). Each rule reads one source text and returns its
|
|
5
|
+
* offences; which files to read is the caller's choice, the rule never varies.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The property a declaration's value belongs to: the last `name:` before the
|
|
9
|
+
* value, with no statement boundary in between. Written as a backwards search
|
|
10
|
+
* rather than a forward `property: value` match so that a value containing its
|
|
11
|
+
* own commas and parentheses — `linear-gradient(90deg, …)`, or the next entry of
|
|
12
|
+
* a JSX style object — cannot make the scan attribute the value to the wrong
|
|
13
|
+
* property or miss it entirely.
|
|
14
|
+
*/
|
|
15
|
+
export function governingProperty(source, valueIndex) {
|
|
16
|
+
const match = /([-A-Za-z][\w-]*)\s*:\s*[^;{}:]*$/.exec(source.slice(0, valueIndex));
|
|
17
|
+
return match?.[1];
|
|
18
|
+
}
|
|
19
|
+
/** Every reference to a destructive role token, in a stylesheet or a JSX inline style. */
|
|
20
|
+
const DESTRUCTIVE_REFERENCE = /var\(\s*--tai-color-(?:on-)?destructive/g;
|
|
21
|
+
/** The one rule whose label sits on the destructive fill. */
|
|
22
|
+
const DESTRUCTIVE_BUTTON_SELECTOR = /^\.tai-btn-danger(?![\w-])/;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the declaration at `valueIndex` sits inside a CSS rule whose every
|
|
25
|
+
* selector is the destructive button. The selector is the text between the
|
|
26
|
+
* previous statement boundary and the innermost open brace, with comments removed.
|
|
27
|
+
*/
|
|
28
|
+
function insideDestructiveButtonRule(source, valueIndex) {
|
|
29
|
+
const before = source.slice(0, valueIndex);
|
|
30
|
+
const open = before.lastIndexOf('{');
|
|
31
|
+
if (open === -1 || before.lastIndexOf('}') > open)
|
|
32
|
+
return false;
|
|
33
|
+
const head = before.slice(0, open);
|
|
34
|
+
const start = Math.max(head.lastIndexOf('}'), head.lastIndexOf('{'), head.lastIndexOf(';')) + 1;
|
|
35
|
+
const selectors = head
|
|
36
|
+
.slice(start)
|
|
37
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
38
|
+
.split(',')
|
|
39
|
+
.map((selector) => selector.trim());
|
|
40
|
+
return selectors.every((selector) => DESTRUCTIVE_BUTTON_SELECTOR.test(selector));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The properties in `source` that paint text with a destructive role token, in the
|
|
44
|
+
* order they appear. The destructive tokens are the fill, its hover and the label on
|
|
45
|
+
* that fill: a fill colour as text fails contrast on the dark grounds, so error text
|
|
46
|
+
* takes `--tai-color-err-text` instead. A `color` declaration (CSS or JSX) is an
|
|
47
|
+
* offence anywhere but the `.tai-btn-danger` rule, where the label colour belongs. A
|
|
48
|
+
* reference whose property cannot be read is reported as `<no property found>`: an
|
|
49
|
+
* unattributable declaration is a hole in the rule, not a pass.
|
|
50
|
+
*/
|
|
51
|
+
export function destructiveColorOffences(source) {
|
|
52
|
+
const offences = [];
|
|
53
|
+
for (const match of source.matchAll(DESTRUCTIVE_REFERENCE)) {
|
|
54
|
+
const property = governingProperty(source, match.index);
|
|
55
|
+
if (property === undefined) {
|
|
56
|
+
offences.push('<no property found>');
|
|
57
|
+
}
|
|
58
|
+
else if (property === 'color' && !insideDestructiveButtonRule(source, match.index)) {
|
|
59
|
+
offences.push(property);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return offences;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=token-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-rules.js","sourceRoot":"","sources":["../src/token-rules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,UAAkB;IAClE,MAAM,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACpF,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,0FAA0F;AAC1F,MAAM,qBAAqB,GAAG,0CAA0C,CAAC;AAEzE,6DAA6D;AAC7D,MAAM,2BAA2B,GAAG,4BAA4B,CAAC;AAEjE;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,MAAc,EAAE,UAAkB;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAChG,MAAM,SAAS,GAAG,IAAI;SACnB,KAAK,CAAC,KAAK,CAAC;SACZ,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,2BAA2B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAc;IACrD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACxD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,2BAA2B,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACrF,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tai42/studio-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "26.0.0",
|
|
4
4
|
"description": "The Studio plugin contract, shared UI components, and hooks that every feature builds on.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "tai42 <oss@tai42.ai>",
|
|
@@ -80,23 +80,23 @@
|
|
|
80
80
|
"@types/node": "^22.10.5",
|
|
81
81
|
"@types/react": "^19.3.0",
|
|
82
82
|
"@types/react-dom": "^19.3.0",
|
|
83
|
-
"@vitejs/plugin-react": "^
|
|
83
|
+
"@vitejs/plugin-react": "^5.2.0",
|
|
84
84
|
"@vitest/coverage-v8": "^5.0.4",
|
|
85
85
|
"jsdom": "^30.0.1",
|
|
86
|
-
"react": "^19.3.
|
|
87
|
-
"react-dom": "^19.3.
|
|
88
|
-
"vitest": "^5.0.
|
|
86
|
+
"react": "^19.3.1",
|
|
87
|
+
"react-dom": "^19.3.1",
|
|
88
|
+
"vitest": "^5.0.5"
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@fontsource-variable/geist-mono": "^5.2.7",
|
|
92
92
|
"@fontsource-variable/inter": "^5.2.8",
|
|
93
|
-
"@radix-ui/react-checkbox": "^1.
|
|
93
|
+
"@radix-ui/react-checkbox": "^1.4.1",
|
|
94
94
|
"@radix-ui/react-dialog": "^1.2.0",
|
|
95
95
|
"@radix-ui/react-radio-group": "^1.4.8",
|
|
96
96
|
"@radix-ui/react-select": "^2.3.8",
|
|
97
97
|
"@radix-ui/react-tabs": "^1.1.22",
|
|
98
98
|
"@radix-ui/react-tooltip": "^1.3.0",
|
|
99
|
-
"@tai42/api-client": "
|
|
99
|
+
"@tai42/api-client": "26.0.0"
|
|
100
100
|
},
|
|
101
101
|
"scripts": {
|
|
102
102
|
"build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ActionMenu` — a row's or a card's overflow menu: a ghost "Actions" button
|
|
3
|
+
* whose accessible name the caller completes ("Actions for <name>", which keeps
|
|
4
|
+
* the visible word at its start) that opens a `role="menu"` list of
|
|
5
|
+
* `role="menuitem"` buttons, the WAI-ARIA menu-button pattern.
|
|
6
|
+
*
|
|
7
|
+
* Opening moves focus to the first item; ArrowDown/ArrowUp move between items
|
|
8
|
+
* (wrapping), Home/End jump to the ends, Escape closes and returns focus to the
|
|
9
|
+
* trigger, Tab closes. Activating an item closes the menu, returns focus to the
|
|
10
|
+
* trigger, then runs the item's action, so a dialog the action opens takes focus
|
|
11
|
+
* from a settled trigger. A pointer press outside the menu closes it.
|
|
12
|
+
*
|
|
13
|
+
* The caller passes only the items the session may use; with none the menu is
|
|
14
|
+
* not drawn at all (a hidden control takes no line).
|
|
15
|
+
*/
|
|
16
|
+
import { type KeyboardEvent, type ReactNode, useEffect, useId, useRef, useState } from 'react';
|
|
17
|
+
|
|
18
|
+
import { ChevronDownIcon } from './icons';
|
|
19
|
+
|
|
20
|
+
export interface ActionMenuItem {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly onSelect: () => void;
|
|
24
|
+
/** Paints the item in the error tone (a destructive action). */
|
|
25
|
+
readonly danger?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ActionMenuProps {
|
|
29
|
+
/** The trigger's accessible name, e.g. "Actions for Acme". */
|
|
30
|
+
readonly label: string;
|
|
31
|
+
readonly items: readonly ActionMenuItem[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ActionMenu({ label, items }: ActionMenuProps): ReactNode {
|
|
35
|
+
const [open, setOpen] = useState(false);
|
|
36
|
+
const menuId = useId();
|
|
37
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
38
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
39
|
+
const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!open) return undefined;
|
|
43
|
+
itemRefs.current[0]?.focus();
|
|
44
|
+
const onPointerDown = (event: PointerEvent): void => {
|
|
45
|
+
const target = event.target as Node | null;
|
|
46
|
+
if (target === null) return;
|
|
47
|
+
if (menuRef.current?.contains(target) === true) return;
|
|
48
|
+
if (triggerRef.current?.contains(target) === true) return;
|
|
49
|
+
setOpen(false);
|
|
50
|
+
};
|
|
51
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
52
|
+
return () => {
|
|
53
|
+
document.removeEventListener('pointerdown', onPointerDown);
|
|
54
|
+
};
|
|
55
|
+
}, [open]);
|
|
56
|
+
|
|
57
|
+
if (items.length === 0) return null;
|
|
58
|
+
|
|
59
|
+
const close = (): void => {
|
|
60
|
+
setOpen(false);
|
|
61
|
+
triggerRef.current?.focus();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const focusItem = (index: number): void => {
|
|
65
|
+
const count = items.length;
|
|
66
|
+
itemRefs.current[((index % count) + count) % count]?.focus();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const onMenuKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
|
|
70
|
+
const current = itemRefs.current.findIndex((item) => item === document.activeElement);
|
|
71
|
+
switch (event.key) {
|
|
72
|
+
case 'ArrowDown':
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
focusItem(current + 1);
|
|
75
|
+
break;
|
|
76
|
+
case 'ArrowUp':
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
focusItem(current - 1);
|
|
79
|
+
break;
|
|
80
|
+
case 'Home':
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
focusItem(0);
|
|
83
|
+
break;
|
|
84
|
+
case 'End':
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
focusItem(items.length - 1);
|
|
87
|
+
break;
|
|
88
|
+
case 'Escape':
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
event.stopPropagation();
|
|
91
|
+
close();
|
|
92
|
+
break;
|
|
93
|
+
case 'Tab':
|
|
94
|
+
setOpen(false);
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="tai-action-menu">
|
|
103
|
+
<button
|
|
104
|
+
ref={triggerRef}
|
|
105
|
+
type="button"
|
|
106
|
+
className="tai-btn tai-btn-ghost"
|
|
107
|
+
aria-label={label}
|
|
108
|
+
aria-haspopup="menu"
|
|
109
|
+
aria-expanded={open}
|
|
110
|
+
aria-controls={open ? menuId : undefined}
|
|
111
|
+
onClick={() => {
|
|
112
|
+
setOpen((was) => !was);
|
|
113
|
+
}}
|
|
114
|
+
onKeyDown={(event) => {
|
|
115
|
+
if (event.key === 'ArrowDown' && !open) {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
setOpen(true);
|
|
118
|
+
}
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
Actions
|
|
122
|
+
<ChevronDownIcon />
|
|
123
|
+
</button>
|
|
124
|
+
{open ? (
|
|
125
|
+
<div
|
|
126
|
+
ref={menuRef}
|
|
127
|
+
id={menuId}
|
|
128
|
+
role="menu"
|
|
129
|
+
tabIndex={-1}
|
|
130
|
+
aria-label={label}
|
|
131
|
+
className="tai-action-menu-list"
|
|
132
|
+
onKeyDown={onMenuKeyDown}
|
|
133
|
+
>
|
|
134
|
+
{items.map((item, index) => (
|
|
135
|
+
<button
|
|
136
|
+
key={item.id}
|
|
137
|
+
ref={(node) => {
|
|
138
|
+
itemRefs.current[index] = node;
|
|
139
|
+
}}
|
|
140
|
+
type="button"
|
|
141
|
+
role="menuitem"
|
|
142
|
+
tabIndex={-1}
|
|
143
|
+
className={
|
|
144
|
+
item.danger === true
|
|
145
|
+
? 'tai-action-menu-item tai-action-menu-item-danger'
|
|
146
|
+
: 'tai-action-menu-item'
|
|
147
|
+
}
|
|
148
|
+
onClick={() => {
|
|
149
|
+
close();
|
|
150
|
+
item.onSelect();
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
{item.label}
|
|
154
|
+
</button>
|
|
155
|
+
))}
|
|
156
|
+
</div>
|
|
157
|
+
) : null}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `AgentPicker` — picks a registered agent by name from the agent list read
|
|
3
|
+
* (`GET /api/agents`) the host owns and hands in. Presentational: it fetches nothing
|
|
4
|
+
* and names no query library. It draws the read's states and its own copy, and flags
|
|
5
|
+
* a stored agent the loaded list does not hold as "not registered".
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentListing } from '@tai42/api-client';
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
|
|
10
|
+
import { AppLink } from '../navigation/app-link';
|
|
11
|
+
import { type ListReadFlags, type NamedListCopy, NamedListPicker } from './named-list-picker';
|
|
12
|
+
|
|
13
|
+
/** The subset of the agent list read this picker reads; a TanStack query result satisfies it. */
|
|
14
|
+
export interface AgentListQuery extends ListReadFlags {
|
|
15
|
+
readonly data: AgentListing | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AgentPickerProps {
|
|
19
|
+
readonly query: AgentListQuery;
|
|
20
|
+
readonly value: string;
|
|
21
|
+
readonly onValueChange: (value: string) => void;
|
|
22
|
+
/** The host form's field error, shown under the control while a `Select` renders. */
|
|
23
|
+
readonly error?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const COPY: NamedListCopy = {
|
|
27
|
+
label: 'Agent',
|
|
28
|
+
loadingPlaceholder: 'Loading agents…',
|
|
29
|
+
placeholder: 'Select an agent',
|
|
30
|
+
forbiddenNote:
|
|
31
|
+
'This session cannot list agents. Picking one needs read access to GET /api/agents; ask an administrator to grant it.',
|
|
32
|
+
emptyNote: 'No agents are registered on this deployment.',
|
|
33
|
+
emptyAction: (
|
|
34
|
+
<AppLink to="agents" className="tai-link">
|
|
35
|
+
Open the Agents screen
|
|
36
|
+
</AppLink>
|
|
37
|
+
),
|
|
38
|
+
unlistedLabel: 'not registered',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function AgentPicker({ query, value, onValueChange, error }: AgentPickerProps): ReactNode {
|
|
42
|
+
const names = (query.data?.items ?? []).map((item) => item.name);
|
|
43
|
+
return (
|
|
44
|
+
<NamedListPicker
|
|
45
|
+
query={query}
|
|
46
|
+
names={names}
|
|
47
|
+
value={value}
|
|
48
|
+
onValueChange={onValueChange}
|
|
49
|
+
error={error}
|
|
50
|
+
copy={COPY}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ChannelPicker` — picks an installed channel plugin by name from the channel list
|
|
3
|
+
* read (`GET /api/channels`) the host owns and hands in. Presentational: it fetches
|
|
4
|
+
* nothing and names no query library. It draws the read's states and its own copy,
|
|
5
|
+
* and flags a stored channel the loaded list does not hold as "not installed".
|
|
6
|
+
*/
|
|
7
|
+
import type { Channels } from '@tai42/api-client';
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
|
|
10
|
+
import { AppLink } from '../navigation/app-link';
|
|
11
|
+
import { type ListReadFlags, type NamedListCopy, NamedListPicker } from './named-list-picker';
|
|
12
|
+
|
|
13
|
+
/** The subset of the channel list read this picker reads; a TanStack query result satisfies it. */
|
|
14
|
+
export interface ChannelListQuery extends ListReadFlags {
|
|
15
|
+
readonly data: Channels | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ChannelPickerProps {
|
|
19
|
+
readonly query: ChannelListQuery;
|
|
20
|
+
readonly value: string;
|
|
21
|
+
readonly onValueChange: (value: string) => void;
|
|
22
|
+
/** The host form's field error, shown under the control while a `Select` renders. */
|
|
23
|
+
readonly error?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The named channel's declared hint for a route's identity: the hint, `null` for a
|
|
28
|
+
* listed channel that declares none, `undefined` when the name is not listed.
|
|
29
|
+
*/
|
|
30
|
+
export function channelIdentityHint(data: Channels, name: string): string | null | undefined {
|
|
31
|
+
const entry = data.channels.find((channel) => channel.name === name);
|
|
32
|
+
return entry === undefined ? undefined : entry.identity_hint;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const COPY: NamedListCopy = {
|
|
36
|
+
label: 'Channel',
|
|
37
|
+
description: 'An installed channel plugin; the answer goes back on the same channel.',
|
|
38
|
+
loadingPlaceholder: 'Loading channels…',
|
|
39
|
+
placeholder: 'Select a channel',
|
|
40
|
+
forbiddenNote:
|
|
41
|
+
'This session cannot list channels. Picking one needs read access to GET /api/channels; ask an administrator to grant it.',
|
|
42
|
+
emptyNote: 'No channels are installed on this deployment.',
|
|
43
|
+
emptyAction: (
|
|
44
|
+
<AppLink to="marketplace" search={{ kind: 'channel' }} className="tai-link">
|
|
45
|
+
Browse channel plugins
|
|
46
|
+
</AppLink>
|
|
47
|
+
),
|
|
48
|
+
unlistedLabel: 'not installed',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function ChannelPicker({
|
|
52
|
+
query,
|
|
53
|
+
value,
|
|
54
|
+
onValueChange,
|
|
55
|
+
error,
|
|
56
|
+
}: ChannelPickerProps): ReactNode {
|
|
57
|
+
const names = (query.data?.channels ?? []).map((channel) => channel.name);
|
|
58
|
+
return (
|
|
59
|
+
<NamedListPicker
|
|
60
|
+
query={query}
|
|
61
|
+
names={names}
|
|
62
|
+
value={value}
|
|
63
|
+
onValueChange={onValueChange}
|
|
64
|
+
error={error}
|
|
65
|
+
copy={COPY}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -167,6 +167,8 @@
|
|
|
167
167
|
/* Radix hard-codes `tabIndex: 0` on every `TabsContent`, so a tab panel is a
|
|
168
168
|
tab stop whether or not it holds one — it needs the ring like any other. */
|
|
169
169
|
.tai-tabpanel:focus-visible,
|
|
170
|
+
.tai-stepper-button:focus-visible,
|
|
171
|
+
.tai-action-menu-item:focus-visible,
|
|
170
172
|
.tai-dialog:focus-visible {
|
|
171
173
|
outline: 2px solid var(--tai-color-focus-ring);
|
|
172
174
|
outline-offset: 2px;
|
|
@@ -279,14 +281,14 @@
|
|
|
279
281
|
}
|
|
280
282
|
|
|
281
283
|
.tai-btn-danger {
|
|
282
|
-
background: var(--tai-color-
|
|
283
|
-
border-color: var(--tai-color-
|
|
284
|
-
color: var(--tai-color-
|
|
284
|
+
background: var(--tai-color-destructive);
|
|
285
|
+
border-color: var(--tai-color-destructive);
|
|
286
|
+
color: var(--tai-color-on-destructive);
|
|
285
287
|
}
|
|
286
288
|
|
|
287
289
|
.tai-btn-danger:hover:not(:disabled):not([aria-disabled='true']) {
|
|
288
|
-
background: var(--tai-color-
|
|
289
|
-
border-color: var(--tai-color-
|
|
290
|
+
background: var(--tai-color-destructive-hover);
|
|
291
|
+
border-color: var(--tai-color-destructive-hover);
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
/* A square button whose only content is a 16 px icon; its accessible name
|
|
@@ -331,6 +333,34 @@
|
|
|
331
333
|
color: var(--tai-color-accent-hover);
|
|
332
334
|
}
|
|
333
335
|
|
|
336
|
+
/* -- Holder list ----------------------------------------------------------
|
|
337
|
+
The things that still use a name, each a link to where it lives. A long
|
|
338
|
+
list scrolls inside its own bordered region, which takes focus so the
|
|
339
|
+
keyboard can scroll it. */
|
|
340
|
+
.tai-holder-list {
|
|
341
|
+
padding: var(--tai-space-2) var(--tai-space-3);
|
|
342
|
+
max-height: 16rem;
|
|
343
|
+
overflow-y: auto;
|
|
344
|
+
color: var(--tai-color-text);
|
|
345
|
+
border: 1px solid var(--tai-color-border);
|
|
346
|
+
border-radius: var(--tai-radius-sm);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.tai-holder-list > ul {
|
|
350
|
+
margin: 0;
|
|
351
|
+
padding: 0;
|
|
352
|
+
list-style: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.tai-holder-list > ul > li + li {
|
|
356
|
+
margin-top: var(--tai-space-1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.tai-holder-lead {
|
|
360
|
+
margin: 0 0 var(--tai-space-2);
|
|
361
|
+
color: var(--tai-color-err-text);
|
|
362
|
+
}
|
|
363
|
+
|
|
334
364
|
/* -- Icons ----------------------------------------------------------------
|
|
335
365
|
Rendered at 16 px in currentColor at full opacity; a 24-unit viewBox and a
|
|
336
366
|
1.6 stroke keep the whole set optically consistent. */
|
|
@@ -362,7 +392,9 @@
|
|
|
362
392
|
.tai-nav-link > svg,
|
|
363
393
|
.tai-segment > svg,
|
|
364
394
|
.tai-select-trigger > svg,
|
|
365
|
-
.tai-status > svg
|
|
395
|
+
.tai-status > svg,
|
|
396
|
+
.tai-stepper-button > svg,
|
|
397
|
+
.tai-action-menu-item > svg {
|
|
366
398
|
flex: none;
|
|
367
399
|
}
|
|
368
400
|
|
|
@@ -1236,12 +1268,14 @@
|
|
|
1236
1268
|
.tai-table .tai-choice,
|
|
1237
1269
|
.tai-table .tai-nav-item,
|
|
1238
1270
|
.tai-table .tai-nav-link,
|
|
1239
|
-
.tai-table .tai-select-trigger
|
|
1271
|
+
.tai-table .tai-select-trigger,
|
|
1272
|
+
.tai-table .tai-stepper-button,
|
|
1273
|
+
.tai-table .tai-action-menu-item {
|
|
1240
1274
|
min-width: auto;
|
|
1241
1275
|
overflow-wrap: normal;
|
|
1242
1276
|
}
|
|
1243
1277
|
|
|
1244
|
-
/* `.tai-segment` takes the wrap half ONLY. The
|
|
1278
|
+
/* `.tai-segment` takes the wrap half ONLY. The ten above carry
|
|
1245
1279
|
`min-width: 0`, so `auto` is what hands them an intrinsic minimum back;
|
|
1246
1280
|
this one carries a 28 px / 44 px POINTER TARGET (WCAG 2.5.8), and `auto` on
|
|
1247
1281
|
a flex item is the CONTENT-based minimum — for the icon-only option this
|
|
@@ -1838,6 +1872,10 @@
|
|
|
1838
1872
|
gap: var(--tai-space-4);
|
|
1839
1873
|
}
|
|
1840
1874
|
|
|
1875
|
+
.tai-stack-1 {
|
|
1876
|
+
gap: var(--tai-space-1);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1841
1879
|
.tai-stack-2 {
|
|
1842
1880
|
gap: var(--tai-space-2);
|
|
1843
1881
|
}
|
|
@@ -2275,6 +2313,176 @@
|
|
|
2275
2313
|
}
|
|
2276
2314
|
}
|
|
2277
2315
|
|
|
2316
|
+
/* -- Stepper: a flow's step progress. A 24 px marker per step, a 2 px connector
|
|
2317
|
+
between steps (the ok fill after a done step), the label beside it. -- */
|
|
2318
|
+
.tai-stepper {
|
|
2319
|
+
display: flex;
|
|
2320
|
+
flex-wrap: wrap;
|
|
2321
|
+
align-items: center;
|
|
2322
|
+
gap: var(--tai-space-2);
|
|
2323
|
+
margin: 0;
|
|
2324
|
+
padding: 0;
|
|
2325
|
+
list-style: none;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
.tai-stepper-step {
|
|
2329
|
+
display: flex;
|
|
2330
|
+
align-items: center;
|
|
2331
|
+
gap: var(--tai-space-2);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
.tai-stepper-step + .tai-stepper-step::before {
|
|
2335
|
+
content: '';
|
|
2336
|
+
display: block;
|
|
2337
|
+
width: var(--tai-space-6);
|
|
2338
|
+
height: 2px;
|
|
2339
|
+
background: var(--tai-color-border);
|
|
2340
|
+
transition: background-color var(--tai-motion-fast);
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
.tai-stepper-step-done + .tai-stepper-step::before {
|
|
2344
|
+
background: var(--tai-color-ok-fill);
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
.tai-stepper-button {
|
|
2348
|
+
display: inline-flex;
|
|
2349
|
+
align-items: center;
|
|
2350
|
+
gap: var(--tai-space-2);
|
|
2351
|
+
min-width: 0;
|
|
2352
|
+
padding: 0;
|
|
2353
|
+
border: 0;
|
|
2354
|
+
background: none;
|
|
2355
|
+
font: inherit;
|
|
2356
|
+
color: inherit;
|
|
2357
|
+
overflow-wrap: anywhere;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
button.tai-stepper-button {
|
|
2361
|
+
cursor: pointer;
|
|
2362
|
+
border-radius: var(--tai-radius-sm);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
.tai-stepper-marker {
|
|
2366
|
+
display: inline-flex;
|
|
2367
|
+
align-items: center;
|
|
2368
|
+
justify-content: center;
|
|
2369
|
+
flex-shrink: 0;
|
|
2370
|
+
width: 24px;
|
|
2371
|
+
height: 24px;
|
|
2372
|
+
border-radius: 50%;
|
|
2373
|
+
font-size: var(--tai-text-sm);
|
|
2374
|
+
font-weight: 600;
|
|
2375
|
+
transition:
|
|
2376
|
+
background-color var(--tai-motion-fast),
|
|
2377
|
+
box-shadow var(--tai-motion-fast),
|
|
2378
|
+
color var(--tai-motion-fast);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
.tai-stepper-marker > svg {
|
|
2382
|
+
width: 14px;
|
|
2383
|
+
height: 14px;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
.tai-stepper-marker-done {
|
|
2387
|
+
background: var(--tai-color-ok-fill);
|
|
2388
|
+
color: var(--tai-color-on-fill);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
.tai-stepper-marker-current {
|
|
2392
|
+
box-shadow: inset 0 0 0 2px var(--tai-color-heading);
|
|
2393
|
+
color: var(--tai-color-heading);
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
.tai-stepper-marker-upcoming {
|
|
2397
|
+
box-shadow: inset 0 0 0 1px var(--tai-color-border-strong);
|
|
2398
|
+
color: var(--tai-color-text-muted);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
.tai-stepper-marker-error {
|
|
2402
|
+
background: var(--tai-color-err-fill);
|
|
2403
|
+
color: var(--tai-color-on-fill);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
.tai-stepper-step-current .tai-stepper-label {
|
|
2407
|
+
font-weight: 600;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
.tai-stepper-step-upcoming .tai-stepper-label {
|
|
2411
|
+
color: var(--tai-color-text-muted);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
/* The "Step 2 of 3: " prefix is the narrow layout's; the wide row shows every step. */
|
|
2415
|
+
.tai-stepper-position {
|
|
2416
|
+
display: none;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
/* -- ActionMenu: an overflow menu stacked in flow under its trigger. -- */
|
|
2420
|
+
.tai-action-menu {
|
|
2421
|
+
position: relative;
|
|
2422
|
+
display: inline-block;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
.tai-action-menu-list {
|
|
2426
|
+
position: absolute;
|
|
2427
|
+
inset-inline-end: 0;
|
|
2428
|
+
top: calc(100% + var(--tai-space-1));
|
|
2429
|
+
z-index: var(--tai-z-dropdown);
|
|
2430
|
+
display: flex;
|
|
2431
|
+
flex-direction: column;
|
|
2432
|
+
min-width: 10rem;
|
|
2433
|
+
padding: var(--tai-space-1);
|
|
2434
|
+
border: 1px solid var(--tai-color-border);
|
|
2435
|
+
border-radius: var(--tai-radius-md);
|
|
2436
|
+
background: var(--tai-color-surface-raised);
|
|
2437
|
+
box-shadow: var(--tai-shadow-lift);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
.tai-action-menu-item {
|
|
2441
|
+
display: flex;
|
|
2442
|
+
align-items: center;
|
|
2443
|
+
min-width: 0;
|
|
2444
|
+
padding: var(--tai-space-2) var(--tai-space-3);
|
|
2445
|
+
border: 0;
|
|
2446
|
+
border-radius: var(--tai-radius-sm);
|
|
2447
|
+
background: none;
|
|
2448
|
+
font: inherit;
|
|
2449
|
+
font-size: var(--tai-text-md);
|
|
2450
|
+
color: var(--tai-color-text);
|
|
2451
|
+
text-align: start;
|
|
2452
|
+
overflow-wrap: anywhere;
|
|
2453
|
+
cursor: pointer;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
.tai-action-menu-item:hover,
|
|
2457
|
+
.tai-action-menu-item:focus-visible {
|
|
2458
|
+
background: var(--tai-color-surface);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
.tai-action-menu-item-danger {
|
|
2462
|
+
color: var(--tai-color-err-text);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
/* <640: the stepper collapses to its current step, "Step 2 of 3: Pick tools";
|
|
2466
|
+
the other steps stay in the list for assistive technology. */
|
|
2467
|
+
@media (max-width: 639px) {
|
|
2468
|
+
.tai-stepper-step:not(.tai-stepper-step-current) {
|
|
2469
|
+
position: absolute;
|
|
2470
|
+
width: 1px;
|
|
2471
|
+
height: 1px;
|
|
2472
|
+
overflow: hidden;
|
|
2473
|
+
clip-path: inset(50%);
|
|
2474
|
+
white-space: nowrap;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
.tai-stepper-step + .tai-stepper-step::before {
|
|
2478
|
+
display: none;
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
.tai-stepper-position {
|
|
2482
|
+
display: inline;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2278
2486
|
/* <640 phone: the rail gives way to a sticky 56 px top bar; the shell, the card
|
|
2279
2487
|
grid and the page header each fall to a single column, and the page tightens
|
|
2280
2488
|
its padding and its gap. */
|
|
@@ -2343,6 +2551,11 @@
|
|
|
2343
2551
|
min-height: var(--tai-control-height-coarse);
|
|
2344
2552
|
}
|
|
2345
2553
|
|
|
2554
|
+
.tai-stepper-button,
|
|
2555
|
+
.tai-action-menu-item {
|
|
2556
|
+
min-height: var(--tai-control-height-coarse);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2346
2559
|
.tai-icon-btn {
|
|
2347
2560
|
width: var(--tai-control-height-coarse);
|
|
2348
2561
|
height: var(--tai-control-height-coarse);
|
|
@@ -2417,6 +2630,11 @@
|
|
|
2417
2630
|
transition: none;
|
|
2418
2631
|
}
|
|
2419
2632
|
|
|
2633
|
+
.tai-stepper-marker,
|
|
2634
|
+
.tai-stepper-step + .tai-stepper-step::before {
|
|
2635
|
+
transition: none;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2420
2638
|
.tai-btn:hover:not(:disabled):not([aria-disabled='true']),
|
|
2421
2639
|
.tai-card-interactive:hover,
|
|
2422
2640
|
.tai-card-interactive:focus-within {
|