@dsh-workbench/shared 0.2.12 → 0.2.14
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/dist/components/ChoiceGroup.css +53 -3
- package/dist/components/ChoiceGroup.d.ts +5 -0
- package/dist/components/ChoiceGroup.js +15 -5
- package/dist/components/FormActions.css +13 -0
- package/dist/components/FormActions.d.ts +15 -0
- package/dist/components/FormActions.js +38 -0
- package/dist/components/FormRows.css +69 -0
- package/dist/components/FormRows.d.ts +14 -0
- package/dist/components/FormRows.js +11 -0
- package/dist/components/SaveBar.d.ts +0 -1
- package/dist/components/SaveBar.js +13 -5
- package/dist/components/SelectField.css +12 -4
- package/dist/components/SelectField.d.ts +1 -0
- package/dist/components/SelectField.js +9 -7
- package/dist/components/Spinner.css +6 -0
- package/dist/components/Spinner.d.ts +6 -0
- package/dist/components/Spinner.js +7 -0
- package/dist/components/TextField.css +2 -2
- package/dist/host.d.ts +3 -3
- package/dist/host.js +3 -3
- package/dist/i18n/en.js +9 -0
- package/dist/i18n/index.d.ts +10 -1
- package/dist/i18n/zh.d.ts +9 -0
- package/dist/i18n/zh.js +9 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +8 -4
- package/dist/lib/form-problem.d.ts +2 -0
- package/dist/lib/form-problem.js +20 -0
- package/package.json +2 -1
- package/src/components/ChoiceGroup.css +53 -3
- package/src/components/ChoiceGroup.test.tsx +88 -0
- package/src/components/ChoiceGroup.tsx +70 -28
- package/src/components/FormActions.css +13 -0
- package/src/components/FormActions.test.tsx +157 -0
- package/src/components/FormActions.tsx +75 -0
- package/src/components/FormRows.css +69 -0
- package/src/components/FormRows.test.tsx +138 -0
- package/src/components/FormRows.tsx +39 -0
- package/src/components/SaveBar.tsx +14 -18
- package/src/components/SelectField.css +12 -4
- package/src/components/SelectField.test.tsx +20 -2
- package/src/components/SelectField.tsx +11 -3
- package/src/components/Spinner.css +6 -0
- package/src/components/Spinner.test.tsx +35 -0
- package/src/components/Spinner.tsx +19 -0
- package/src/components/TextField.css +2 -2
- package/src/host.ts +3 -3
- package/src/i18n/en.ts +9 -0
- package/src/i18n/zh.ts +9 -0
- package/src/index.ts +8 -4
- package/src/lib/form-problem.test.ts +76 -0
- package/src/lib/form-problem.ts +22 -0
- package/src/components/SaveBar.css +0 -7
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
5
|
gap: var(--wb-len-2);
|
|
6
|
+
background: transparent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.wb-choiceMenu[data-variant='boxed'] {
|
|
6
10
|
padding: var(--wb-len-4);
|
|
7
11
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
8
12
|
border-radius: var(--dsw-radius-md);
|
|
9
|
-
background: transparent;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
.wb-choiceOption {
|
|
@@ -14,8 +17,8 @@
|
|
|
14
17
|
display: flex;
|
|
15
18
|
align-items: center;
|
|
16
19
|
gap: var(--wb-len-6);
|
|
17
|
-
min-height: var(--wb-len-
|
|
18
|
-
padding: var(--wb-len-
|
|
20
|
+
min-height: var(--wb-len-28);
|
|
21
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
19
22
|
border-radius: var(--dsw-radius-md);
|
|
20
23
|
cursor: pointer;
|
|
21
24
|
font-size: var(--dsw-font-xs-13-font-size);
|
|
@@ -87,12 +90,59 @@
|
|
|
87
90
|
|
|
88
91
|
.wb-choiceLabel {
|
|
89
92
|
flex: 1;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.wb-choiceLabelText {
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
text-overflow: ellipsis;
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.wb-choiceLabel:has(.wb-choiceDescription) .wb-choiceLabelText {
|
|
106
|
+
overflow: visible;
|
|
107
|
+
white-space: normal;
|
|
108
|
+
overflow-wrap: anywhere;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.wb-choiceDescription {
|
|
112
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
113
|
+
line-height: var(--wb-line-18);
|
|
114
|
+
color: var(--dsw-alias-label-tertiary);
|
|
115
|
+
overflow-wrap: anywhere;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.wb-choiceLoading {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: var(--wb-len-6);
|
|
122
|
+
min-height: var(--wb-len-28);
|
|
123
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
124
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
125
|
+
line-height: var(--wb-line-20);
|
|
126
|
+
color: var(--dsw-alias-label-secondary);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.wb-choiceLoadingText {
|
|
90
130
|
min-width: 0;
|
|
91
131
|
overflow: hidden;
|
|
92
132
|
text-overflow: ellipsis;
|
|
93
133
|
white-space: nowrap;
|
|
94
134
|
}
|
|
95
135
|
|
|
136
|
+
.wb-choiceEmpty {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
min-height: var(--wb-len-28);
|
|
140
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
141
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
142
|
+
line-height: var(--wb-line-20);
|
|
143
|
+
color: var(--dsw-alias-label-tertiary);
|
|
144
|
+
}
|
|
145
|
+
|
|
96
146
|
.wb-radio {
|
|
97
147
|
appearance: none;
|
|
98
148
|
-webkit-appearance: none;
|
|
@@ -3,12 +3,17 @@ import './ChoiceGroup.css';
|
|
|
3
3
|
export interface ChoiceGroupOption {
|
|
4
4
|
readonly value: string;
|
|
5
5
|
readonly label: ReactNode;
|
|
6
|
+
readonly description?: ReactNode | undefined;
|
|
6
7
|
}
|
|
7
8
|
interface ChoiceGroupCommon {
|
|
8
9
|
readonly label: string;
|
|
9
10
|
readonly name: string;
|
|
10
11
|
readonly options: readonly ChoiceGroupOption[];
|
|
11
12
|
readonly disabled?: boolean | undefined;
|
|
13
|
+
readonly variant?: 'boxed' | 'ghost' | undefined;
|
|
14
|
+
readonly loading?: boolean | undefined;
|
|
15
|
+
readonly loadingText?: ReactNode | undefined;
|
|
16
|
+
readonly emptyText?: ReactNode | undefined;
|
|
12
17
|
}
|
|
13
18
|
export interface ChoiceGroupSingleProps extends ChoiceGroupCommon {
|
|
14
19
|
readonly multi?: false | undefined;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { Fragment, h } from '../lib/react.js';
|
|
2
2
|
import { IconCheckOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Spinner } from './Spinner.js';
|
|
3
4
|
import './ChoiceGroup.css';
|
|
4
5
|
function ChoiceMenu(input) {
|
|
5
|
-
|
|
6
|
+
const body = input.loading ? (h("span", { className: "wb-choiceLoading", role: "status" },
|
|
7
|
+
h(Spinner, { size: 14 }),
|
|
8
|
+
h("span", { className: "wb-choiceLoadingText" }, input.loadingText ?? '…'))) : input.options.length === 0 ? (input.emptyText === undefined ? null : (h("span", { className: "wb-choiceEmpty" }, input.emptyText))) : (input.options.map((option) => {
|
|
6
9
|
const row = input.rowOf(option.value);
|
|
7
10
|
return (h(Fragment, { key: option.value },
|
|
8
11
|
h("label", { className: `wb-choiceOption${row.checked ? ' wb-choiceOptionOn' : ''}` },
|
|
9
12
|
h("input", { className: input.multi ? 'wb-checkbox' : 'wb-radio', type: input.multi ? 'checkbox' : 'radio', name: input.name, checked: row.checked, disabled: input.disabled, onChange: row.onSelect }),
|
|
10
13
|
input.multi ? (h("span", { className: "wb-checkboxBox", "aria-hidden": "true" }, row.checked ? h(IconCheckOutlineRegular, { size: 12 }) : null)) : null,
|
|
11
|
-
h("span", { className: "wb-choiceLabel" },
|
|
12
|
-
|
|
14
|
+
h("span", { className: "wb-choiceLabel" },
|
|
15
|
+
h("span", { className: "wb-choiceLabelText" }, option.label),
|
|
16
|
+
option.description === undefined ? null : (h("span", { className: "wb-choiceDescription" }, option.description))))));
|
|
17
|
+
}));
|
|
18
|
+
return (h("div", { className: "wb-choiceMenu", "data-variant": input.variant, role: input.multi ? 'group' : 'radiogroup', "aria-label": input.label, "aria-busy": input.loading ? true : undefined }, body));
|
|
13
19
|
}
|
|
14
20
|
export function ChoiceGroup(props) {
|
|
15
21
|
const common = {
|
|
@@ -17,11 +23,15 @@ export function ChoiceGroup(props) {
|
|
|
17
23
|
name: props.name,
|
|
18
24
|
options: props.options,
|
|
19
25
|
disabled: props.disabled === true,
|
|
26
|
+
variant: props.variant ?? 'boxed',
|
|
27
|
+
loading: props.loading === true,
|
|
28
|
+
loadingText: props.loadingText,
|
|
29
|
+
emptyText: props.emptyText,
|
|
20
30
|
};
|
|
21
31
|
if (props.multi === true) {
|
|
22
32
|
const values = props.values;
|
|
23
33
|
const onChange = props.onChange;
|
|
24
|
-
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: true, rowOf: (value) => ({
|
|
34
|
+
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: true, variant: common.variant, loading: common.loading, loadingText: common.loadingText, emptyText: common.emptyText, rowOf: (value) => ({
|
|
25
35
|
checked: values.includes(value),
|
|
26
36
|
onSelect: () => {
|
|
27
37
|
onChange(values.includes(value)
|
|
@@ -32,7 +42,7 @@ export function ChoiceGroup(props) {
|
|
|
32
42
|
}
|
|
33
43
|
const value = props.value;
|
|
34
44
|
const onChange = props.onChange;
|
|
35
|
-
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: false, rowOf: (option) => ({
|
|
45
|
+
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: false, variant: common.variant, loading: common.loading, loadingText: common.loadingText, emptyText: common.emptyText, rowOf: (option) => ({
|
|
36
46
|
checked: option === value,
|
|
37
47
|
onSelect: () => {
|
|
38
48
|
onChange(option);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.wb-formActions {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: flex-end;
|
|
6
|
+
gap: var(--wb-len-8);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.wb-formActionDanger {
|
|
10
|
+
color: var(--dsw-alias-state-error-primary);
|
|
11
|
+
border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent);
|
|
12
|
+
--dsw-alias-interactive-bg-hover: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from '../lib/react.js';
|
|
2
|
+
import './FormActions.css';
|
|
3
|
+
export type FormActionKind = 'default' | 'danger' | 'primary';
|
|
4
|
+
export interface FormAction {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
readonly label: ReactNode;
|
|
7
|
+
readonly kind?: FormActionKind | undefined;
|
|
8
|
+
readonly onClick: () => void;
|
|
9
|
+
readonly disabled?: boolean | undefined;
|
|
10
|
+
readonly busy?: boolean | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface FormActionsProps {
|
|
13
|
+
readonly actions: readonly FormAction[];
|
|
14
|
+
}
|
|
15
|
+
export declare function FormActions({ actions }: FormActionsProps): JSX.Element | null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { h } from '../lib/react.js';
|
|
2
|
+
import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Spinner } from './Spinner.js';
|
|
4
|
+
import './FormActions.css';
|
|
5
|
+
const RANK = { default: 0, danger: 1, primary: 2 };
|
|
6
|
+
function kindOf(action) {
|
|
7
|
+
return action.kind ?? 'default';
|
|
8
|
+
}
|
|
9
|
+
function ordered(actions) {
|
|
10
|
+
return actions
|
|
11
|
+
.map((action, index) => ({ action, index, rank: RANK[kindOf(action)] }))
|
|
12
|
+
.sort((left, right) => left.rank - right.rank || left.index - right.index)
|
|
13
|
+
.map((one) => one.action);
|
|
14
|
+
}
|
|
15
|
+
function reportExtraPrimaries(actions) {
|
|
16
|
+
const keys = actions.filter((one) => kindOf(one) === 'primary').map((one) => one.key);
|
|
17
|
+
if (keys.length < 2)
|
|
18
|
+
return;
|
|
19
|
+
console.error(`[dsh-workbench] FormActions 一个区里出现了 ${keys.length} 颗 primary(${keys.join('、')})—— ` +
|
|
20
|
+
'一个区只许一颗主操作(ADR-0054 §2 规则二):多余的这颗该改成 default');
|
|
21
|
+
}
|
|
22
|
+
function variantOf(kind) {
|
|
23
|
+
if (kind === 'primary')
|
|
24
|
+
return 'primary';
|
|
25
|
+
if (kind === 'danger')
|
|
26
|
+
return 'outline';
|
|
27
|
+
return 'ghost';
|
|
28
|
+
}
|
|
29
|
+
export function FormActions({ actions }) {
|
|
30
|
+
if (actions.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
reportExtraPrimaries(actions);
|
|
33
|
+
return (h("div", { className: "wb-formActions" }, ordered(actions).map((action) => {
|
|
34
|
+
const kind = kindOf(action);
|
|
35
|
+
const busy = action.busy === true;
|
|
36
|
+
return (h(Button, { key: action.key, type: "button", size: "sm", variant: variantOf(kind), className: kind === 'danger' ? 'wb-formActionDanger' : undefined, disabled: busy || action.disabled === true, icon: busy ? h(Spinner, { size: 12 }) : undefined, onClick: action.onClick }, action.label));
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.wb-formRows {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: minmax(72px, max-content) minmax(0, 1fr);
|
|
4
|
+
column-gap: var(--wb-len-8);
|
|
5
|
+
row-gap: var(--wb-len-12);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.wb-formRows > :not(.wb-formRow) {
|
|
9
|
+
grid-column: 1 / -1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.wb-formRow {
|
|
13
|
+
display: grid;
|
|
14
|
+
grid-column: 1 / -1;
|
|
15
|
+
grid-template-columns: minmax(72px, max-content) minmax(0, 1fr);
|
|
16
|
+
align-items: center;
|
|
17
|
+
column-gap: var(--wb-len-8);
|
|
18
|
+
row-gap: var(--wb-len-8);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.wb-formRows > .wb-formRow {
|
|
22
|
+
grid-template-columns: subgrid;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.wb-formRow[data-layout='stacked'] {
|
|
26
|
+
grid-template-columns: minmax(0, 1fr);
|
|
27
|
+
align-items: stretch;
|
|
28
|
+
gap: var(--wb-len-6);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.wb-formLabel {
|
|
32
|
+
min-width: 0;
|
|
33
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
34
|
+
color: var(--dsw-alias-label-secondary);
|
|
35
|
+
overflow-wrap: anywhere;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.wb-formRow[data-layout='stacked'] .wb-formLabel {
|
|
40
|
+
white-space: normal;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.wb-formControl {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
min-width: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.wb-formHint {
|
|
50
|
+
grid-column: 2;
|
|
51
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
52
|
+
line-height: var(--wb-line-18);
|
|
53
|
+
color: var(--dsw-alias-label-tertiary);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.wb-formProblem {
|
|
57
|
+
grid-column: 2;
|
|
58
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
59
|
+
line-height: var(--wb-line-18);
|
|
60
|
+
color: var(--dsw-alias-state-error-primary);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.wb-formRow:has(.wb-formProblem) .wb-formLabel {
|
|
64
|
+
color: var(--dsw-alias-state-error-primary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.wb-formRow:has(:disabled) .wb-formLabel {
|
|
68
|
+
opacity: 0.4;
|
|
69
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from '../lib/react.js';
|
|
2
|
+
import './FormRows.css';
|
|
3
|
+
export interface FormRowsProps {
|
|
4
|
+
readonly children?: ReactNode | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function FormRows({ children }: FormRowsProps): JSX.Element;
|
|
7
|
+
export interface FormRowProps {
|
|
8
|
+
readonly label: ReactNode;
|
|
9
|
+
readonly hint?: ReactNode | undefined;
|
|
10
|
+
readonly problem?: ReactNode | undefined;
|
|
11
|
+
readonly layout?: 'inline' | 'stacked' | undefined;
|
|
12
|
+
readonly children?: ReactNode | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function FormRow({ label, hint, problem, layout, children }: FormRowProps): JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { h } from '../lib/react.js';
|
|
2
|
+
import './FormRows.css';
|
|
3
|
+
export function FormRows({ children }) {
|
|
4
|
+
return h("div", { className: "wb-formRows" }, children);
|
|
5
|
+
}
|
|
6
|
+
export function FormRow({ label, hint, problem, layout = 'inline', children }) {
|
|
7
|
+
return (h("label", { className: "wb-formRow", "data-layout": layout, "data-wb-problem": problem === undefined ? undefined : '' },
|
|
8
|
+
h("span", { className: "wb-formLabel" }, label),
|
|
9
|
+
h("span", { className: "wb-formControl" }, children),
|
|
10
|
+
problem === undefined ? (hint === undefined ? null : (h("span", { className: "wb-formHint" }, hint))) : (h("span", { className: "wb-formProblem" }, problem))));
|
|
11
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
2
1
|
import { h } from '../lib/react.js';
|
|
3
|
-
import './
|
|
2
|
+
import { FormActions } from './FormActions.js';
|
|
4
3
|
export function SaveBar({ busy, disabled, labels, onCancel, onSave }) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const actions = [
|
|
5
|
+
{ key: 'cancel', label: labels.cancel, kind: 'default', onClick: onCancel, disabled: busy },
|
|
6
|
+
{
|
|
7
|
+
key: 'save',
|
|
8
|
+
label: busy ? labels.saving : labels.save,
|
|
9
|
+
kind: 'primary',
|
|
10
|
+
onClick: onSave,
|
|
11
|
+
disabled: busy || disabled === true,
|
|
12
|
+
busy,
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
return h(FormActions, { actions: actions });
|
|
8
16
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
align-items: center;
|
|
5
5
|
gap: var(--wb-len-8);
|
|
6
6
|
width: 100%;
|
|
7
|
-
min-height: var(--wb-len-
|
|
8
|
-
padding: var(--wb-len-
|
|
7
|
+
min-height: var(--wb-len-28);
|
|
8
|
+
padding: var(--wb-len-4) var(--wb-len-10);
|
|
9
9
|
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
10
10
|
border-radius: var(--dsw-radius-md);
|
|
11
11
|
background: var(--dsw-alias-bg-layer-3);
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-business-primary);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
.wb-selectTrigger[aria-invalid='true'] {
|
|
25
|
+
border-color: var(--dsw-alias-state-error-primary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.wb-selectTrigger[aria-invalid='true']:focus-visible {
|
|
29
|
+
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-error-primary);
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
.wb-selectTrigger:disabled {
|
|
25
33
|
cursor: not-allowed;
|
|
26
34
|
opacity: 0.4;
|
|
@@ -100,8 +108,8 @@
|
|
|
100
108
|
align-items: center;
|
|
101
109
|
gap: var(--wb-len-8);
|
|
102
110
|
width: 100%;
|
|
103
|
-
min-height: var(--wb-len-
|
|
104
|
-
padding: var(--wb-len-
|
|
111
|
+
min-height: var(--wb-len-28);
|
|
112
|
+
padding: var(--wb-len-4) var(--wb-len-10);
|
|
105
113
|
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
106
114
|
border-radius: var(--dsw-radius-md);
|
|
107
115
|
background: var(--dsw-alias-bg-layer-3);
|
|
@@ -34,6 +34,7 @@ export interface SelectFieldProps {
|
|
|
34
34
|
readonly open?: boolean | undefined;
|
|
35
35
|
readonly onOpenChange?: ((open: boolean) => void) | undefined;
|
|
36
36
|
readonly disabled?: boolean | undefined;
|
|
37
|
+
readonly invalid?: boolean | undefined;
|
|
37
38
|
readonly loading?: boolean | undefined;
|
|
38
39
|
readonly loadingText?: string | undefined;
|
|
39
40
|
readonly fallback?: SelectFieldFallback | undefined;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { h, useEffect, useMemo, useRef, useState } from '../lib/react.js';
|
|
2
|
-
import { IconChevronDownOutlineRegular, IconSearchOutlineRegular, Menu,
|
|
2
|
+
import { IconChevronDownOutlineRegular, IconSearchOutlineRegular, Menu, TextShimmer, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
3
|
import { Field } from './Field.js';
|
|
4
4
|
import { TextField } from './TextField.js';
|
|
5
|
+
import { Spinner } from './Spinner.js';
|
|
5
6
|
import './SelectField.css';
|
|
6
7
|
const FOOTER_ID = '__wb-select-footer__';
|
|
7
8
|
const EMPTY_ID = '__wb-select-empty__';
|
|
@@ -17,18 +18,18 @@ function itemOf(option) {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
function triggerNode(args) {
|
|
20
|
-
return (h("button", { type: "button", id: args.id, className: args.className, disabled: args.disabled, "aria-haspopup": "listbox", "aria-expanded": args.open, "aria-label": args.ariaLabel, title: args.title, onClick: args.onOpen },
|
|
21
|
+
return (h("button", { type: "button", id: args.id, className: args.className, disabled: args.disabled, "aria-haspopup": "listbox", "aria-expanded": args.open, "aria-invalid": args.invalid || undefined, "aria-label": args.ariaLabel, title: args.title, onClick: args.onOpen },
|
|
21
22
|
args.icon === undefined ? null : h("span", { className: "wb-selectIcon" }, args.icon),
|
|
22
23
|
h("span", { className: "wb-selectValue" }, args.value),
|
|
23
24
|
h("span", { className: "wb-selectChevron" },
|
|
24
25
|
h(IconChevronDownOutlineRegular, { size: 14 }))));
|
|
25
26
|
}
|
|
26
27
|
function loadingNode(args) {
|
|
27
|
-
return (h("button", { type: "button", id: args.id, className: `${args.className} wb-selectLoading`, disabled: true, "aria-busy": "true", "aria-label": args.ariaLabel, title: args.loadingText },
|
|
28
|
+
return (h("button", { type: "button", id: args.id, className: `${args.className} wb-selectLoading`, disabled: true, "aria-busy": "true", "aria-invalid": args.invalid || undefined, "aria-label": args.ariaLabel, title: args.loadingText },
|
|
28
29
|
h("span", { className: "wb-selectValue" },
|
|
29
30
|
h(TextShimmer, { active: true }, args.loadingText ?? '…')),
|
|
30
31
|
h("span", { className: "wb-selectChevron" },
|
|
31
|
-
h(
|
|
32
|
+
h(Spinner, { size: 14 }))));
|
|
32
33
|
}
|
|
33
34
|
function searchNode(args) {
|
|
34
35
|
return (h("span", { ref: args.boxRef, className: args.className },
|
|
@@ -154,21 +155,22 @@ function useSelectState(props) {
|
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
export function SelectField(props) {
|
|
157
|
-
const { value, options, onChange, label, hint, icon, renderValue, placeholder, search = true, searchLabel, searchPlaceholder, footer, onFooter, disabled = false, loading = false, loadingText, fallback, selection = 'fill', variant = 'boxed', ariaLabel, title, id, name, className, menuClassName, } = props;
|
|
158
|
+
const { value, options, onChange, label, hint, icon, renderValue, placeholder, search = true, searchLabel, searchPlaceholder, footer, onFooter, disabled = false, invalid = false, loading = false, loadingText, fallback, selection = 'fill', variant = 'boxed', ariaLabel, title, id, name, className, menuClassName, } = props;
|
|
158
159
|
const state = useSelectState(props);
|
|
159
160
|
const current = options.find((option) => option.value === value);
|
|
160
161
|
const variantClass = variant === 'ghost' ? 'wb-selectGhost' : undefined;
|
|
161
162
|
const triggerClass = mergeClass('wb-selectTrigger', variantClass);
|
|
162
163
|
if (loading) {
|
|
163
|
-
return framed(label, hint, className, loadingNode({ id, className: triggerClass, ariaLabel, loadingText }));
|
|
164
|
+
return framed(label, hint, className, loadingNode({ id, className: triggerClass, invalid, ariaLabel, loadingText }));
|
|
164
165
|
}
|
|
165
166
|
if (fallback !== undefined && options.length === 0) {
|
|
166
|
-
return framed(label, hint, className, h(TextField, { id: id, name: name, className: "wb-selectFallback", "aria-label": ariaLabel, value: fallback.value, onChange: (event) => fallback.onChange(event.currentTarget.value) }));
|
|
167
|
+
return framed(label, hint, className, h(TextField, { id: id, name: name, className: "wb-selectFallback", invalid: invalid, "aria-label": ariaLabel, value: fallback.value, onChange: (event) => fallback.onChange(event.currentTarget.value) }));
|
|
167
168
|
}
|
|
168
169
|
const trigger = triggerNode({
|
|
169
170
|
id,
|
|
170
171
|
className: triggerClass,
|
|
171
172
|
disabled,
|
|
173
|
+
invalid,
|
|
172
174
|
open: state.open,
|
|
173
175
|
ariaLabel,
|
|
174
176
|
title,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { StateDot } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
2
|
+
import { h } from '../lib/react.js';
|
|
3
|
+
import './Spinner.css';
|
|
4
|
+
export function Spinner({ size = 14, label }) {
|
|
5
|
+
return (h("span", { className: "wb-spinner", ...(label === undefined ? { 'aria-hidden': true } : { role: 'status', 'aria-label': label }) },
|
|
6
|
+
h(StateDot, { state: "ongoing", size: size })));
|
|
7
|
+
}
|
package/dist/host.d.ts
CHANGED
|
@@ -8,17 +8,17 @@ export * from './lib/text.js';
|
|
|
8
8
|
export * from './lib/ui.js';
|
|
9
9
|
export * from './lib/panel-state.js';
|
|
10
10
|
export * from './api/blocker-table.js';
|
|
11
|
+
export * from './host/browser-find.js';
|
|
12
|
+
export * from './host/browser-read.js';
|
|
11
13
|
export * from './host/cli-error.js';
|
|
12
14
|
export * from './host/data-root.js';
|
|
13
|
-
export * from './host/browser-read.js';
|
|
14
|
-
export * from './host/browser-find.js';
|
|
15
15
|
export * from './host/facts.js';
|
|
16
16
|
export * from './host/git.js';
|
|
17
17
|
export * from './host/http.js';
|
|
18
18
|
export * from './host/long-task.js';
|
|
19
19
|
export * from './host/opencli-runtime.js';
|
|
20
|
-
export * from './host/opencli.js';
|
|
21
20
|
export * from './host/opencli-window.js';
|
|
21
|
+
export * from './host/opencli.js';
|
|
22
22
|
export * from './host/plugin-ledger.js';
|
|
23
23
|
export * from './host/plugin-source.js';
|
|
24
24
|
export * from './host/provision.js';
|
package/dist/host.js
CHANGED
|
@@ -8,17 +8,17 @@ export * from './lib/text.js';
|
|
|
8
8
|
export * from './lib/ui.js';
|
|
9
9
|
export * from './lib/panel-state.js';
|
|
10
10
|
export * from './api/blocker-table.js';
|
|
11
|
+
export * from './host/browser-find.js';
|
|
12
|
+
export * from './host/browser-read.js';
|
|
11
13
|
export * from './host/cli-error.js';
|
|
12
14
|
export * from './host/data-root.js';
|
|
13
|
-
export * from './host/browser-read.js';
|
|
14
|
-
export * from './host/browser-find.js';
|
|
15
15
|
export * from './host/facts.js';
|
|
16
16
|
export * from './host/git.js';
|
|
17
17
|
export * from './host/http.js';
|
|
18
18
|
export * from './host/long-task.js';
|
|
19
19
|
export * from './host/opencli-runtime.js';
|
|
20
|
-
export * from './host/opencli.js';
|
|
21
20
|
export * from './host/opencli-window.js';
|
|
21
|
+
export * from './host/opencli.js';
|
|
22
22
|
export * from './host/plugin-ledger.js';
|
|
23
23
|
export * from './host/plugin-source.js';
|
|
24
24
|
export * from './host/provision.js';
|
package/dist/i18n/en.js
CHANGED
|
@@ -45,6 +45,15 @@ export const en = {
|
|
|
45
45
|
'status.completed': 'Completed',
|
|
46
46
|
'status.subagentsRunning.one': '{n} subagent running',
|
|
47
47
|
'status.subagentsRunning.other': '{n} subagents running',
|
|
48
|
+
'project.attention.warning.one': '1 session waiting for you',
|
|
49
|
+
'project.attention.warning.other': '{n} sessions waiting for you',
|
|
50
|
+
'project.attention.running.one': '1 session running',
|
|
51
|
+
'project.attention.running.other': '{n} sessions running',
|
|
52
|
+
'project.attention.done.one': '1 session finished, unread',
|
|
53
|
+
'project.attention.done.other': '{n} sessions finished, unread',
|
|
54
|
+
'project.attention.state.warning': 'Approval',
|
|
55
|
+
'project.attention.state.running': 'Running',
|
|
56
|
+
'project.attention.state.done': 'New',
|
|
48
57
|
'hover.copied': 'Copied',
|
|
49
58
|
'time.now': 'just now',
|
|
50
59
|
'time.minutes': '{n} min ago',
|