Skip to content

Commit 13bb022

Browse files
authored
Merge pull request #3232 from adobe/aramos-adobe/css868-combobox-readonly
feat(combobox): adding read only state to combobox
2 parents ac321bf + aa3633c commit 13bb022

File tree

7 files changed

+122
-8
lines changed

7 files changed

+122
-8
lines changed

.changeset/new-doors-fold.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@spectrum-css/combobox": minor
3+
---
4+
5+
Combobox now includes a `read-only` state. Add the `is-readOnly` class to the Combobox to enable this feature. To ensure the intended user experience, please make sure the readonly state in the textfield is enabled by adding the `is-readOnly` class and `readonly` attribute on the input element for this component as well.

components/combobox/index.css

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@
9090
--mod-picker-button-background-color-disabled: var(--mod-combobox-background-color-disabled);
9191
--mod-picker-button-font-color-disabled: var(--mod-combobox-font-color-disabled);
9292
/* @passthroughs end -- settings for nested Picker Button component */
93+
94+
/*** Read-only Colors ***/
95+
--spectrum-combobox-readonly-input-background-color: var(--spectrum-gray-50);
96+
--spectrum-combobox-readonly-input-border-color: var(--spectrum-gray-500);
97+
--spectrum-combobox-readonly-border-color-invalid-default: var(--spectrum-negative-border-color-default);
98+
--spectrum-combobox-readonly-background-color-disabled: var(--spectrum-disabled-background-color);
99+
--spectrum-combobox-readonly-text-color-disabled: var(--spectrum-disabled-content-color);
100+
--spectrum-combobox-border-color-disabled: var(--spectrum-disabled-border-color);
93101
}
94102

95103
.spectrum-Combobox--sizeS {
@@ -213,6 +221,39 @@
213221
.spectrum-Popover.is-open {
214222
transform: translateY(var(--mod-combobox-spacing-edge-to-menu, var(--spectrum-combobox-spacing-edge-to-menu)));
215223
}
224+
225+
&.is-readOnly:not(.spectrum-Combobox--quiet) {
226+
.spectrum-Combobox-textfield {
227+
&.is-keyboardFocused .spectrum-Combobox-input {
228+
outline-offset: var(--mod-textfield-focus-indicator-gap);
229+
outline: var(--mod-textfield-focus-indicator-width) solid;
230+
outline-color: var(--mod-textfield-focus-indicator-color);
231+
}
232+
}
233+
234+
.spectrum-Combobox-input:read-only {
235+
background-color: var(--spectrum-combobox-readonly-input-background-color);
236+
border-color: var(--spectrum-combobox-readonly-input-border-color);
237+
238+
&:hover {
239+
background-color: revert;
240+
}
241+
}
242+
243+
&.is-invalid .spectrum-Combobox-input:read-only {
244+
border-color: var(--highcontrast-textfield-border-color-invalid-default, var(--mod-textfield-border-color-invalid-default, var(--spectrum-combobox-readonly-border-color-invalid-default)));
245+
}
246+
247+
&.is-disabled .spectrum-Combobox-input:read-only {
248+
background-color: var(--mod-textfield-background-color-disabled, var(--spectrum-combobox-readonly-background-color-disabled));
249+
border-color: transparent;
250+
color: var(--highcontrast-textfield-text-color-disabled, var(--mod-textfield-text-color-disabled, var(--spectrum-combobox-readonly-text-color-disabled)));
251+
252+
&:hover {
253+
background-color: var(--mod-textfield-background-color-disabled, var(--spectrum-combobox-readonly-background-color-disabled));
254+
}
255+
}
256+
}
216257
}
217258

218259
/* LOADING INDICATOR */
@@ -377,6 +418,21 @@
377418
&.is-invalid .spectrum-Textfield-validationIcon {
378419
inset-inline-end: var(--mod-combobox-button-width, var(--spectrum-combobox-button-width));
379420
}
421+
422+
&.is-readOnly {
423+
.spectrum-Combobox-input:read-only {
424+
border-block-end: var(--mod-combobox-border-width, var(--spectrum-combobox-border-width)) solid var(--mod-combobox-readonly-input-border-color, var(--spectrum-combobox-readonly-input-border-color));
425+
}
426+
427+
&.is-invalid > .spectrum-Combobox-input:read-only {
428+
border-color: var(--highcontrast-textfield-border-color-invalid-default, var(--mod-textfield-border-color-invalid-default, var(--spectrum-combobox-readonly-border-color-invalid-default)));
429+
}
430+
431+
&.is-disabled .spectrum-Combobox-input:read-only {
432+
color: var(--highcontrast-textfield-text-color-disabled, var(--mod-textfield-text-color-disabled, var(--spectrum-combobox-readonly-text-color-disabled)));
433+
border-color: var(--mod-textfield-border-color-disabled, var(--spectrum-combobox-border-color-disabled));
434+
}
435+
}
380436
}
381437

382438
.spectrum-Combobox-input {

components/combobox/metadata/metadata.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
".spectrum-Combobox--quiet .spectrum-Combobox-textfield.is-invalid .spectrum-Combobox-input",
1111
".spectrum-Combobox--quiet .spectrum-Combobox-textfield.is-invalid .spectrum-Textfield-validationIcon",
1212
".spectrum-Combobox--quiet .spectrum-Combobox-textfield.is-loading .spectrum-Combobox-input",
13+
".spectrum-Combobox--quiet .spectrum-Combobox-textfield.is-readOnly .spectrum-Combobox-input:read-only",
14+
".spectrum-Combobox--quiet .spectrum-Combobox-textfield.is-readOnly.is-disabled .spectrum-Combobox-input:read-only",
15+
".spectrum-Combobox--quiet .spectrum-Combobox-textfield.is-readOnly.is-invalid > .spectrum-Combobox-input:read-only",
1316
".spectrum-Combobox--quiet.spectrum-Combobox--sizeL",
1417
".spectrum-Combobox--quiet.spectrum-Combobox--sizeM",
1518
".spectrum-Combobox--quiet.spectrum-Combobox--sizeS",
@@ -59,6 +62,12 @@
5962
".spectrum-Combobox.is-focused:hover .spectrum-Combobox-button:not(:disabled, .is-invalid, .spectrum-PickerButton--quiet)",
6063
".spectrum-Combobox.is-keyboardFocused .spectrum-Combobox-button.is-invalid:not(:disabled, .spectrum-PickerButton--quiet)",
6164
".spectrum-Combobox.is-keyboardFocused .spectrum-Combobox-button:not(:disabled, .is-invalid, .spectrum-PickerButton--quiet)",
65+
".spectrum-Combobox.is-readOnly.is-disabled:not(.spectrum-Combobox--quiet) .spectrum-Combobox-input:read-only",
66+
".spectrum-Combobox.is-readOnly.is-disabled:not(.spectrum-Combobox--quiet) .spectrum-Combobox-input:read-only:hover",
67+
".spectrum-Combobox.is-readOnly.is-invalid:not(.spectrum-Combobox--quiet) .spectrum-Combobox-input:read-only",
68+
".spectrum-Combobox.is-readOnly:not(.spectrum-Combobox--quiet) .spectrum-Combobox-input:read-only",
69+
".spectrum-Combobox.is-readOnly:not(.spectrum-Combobox--quiet) .spectrum-Combobox-input:read-only:hover",
70+
".spectrum-Combobox.is-readOnly:not(.spectrum-Combobox--quiet) .spectrum-Combobox-textfield.is-keyboardFocused .spectrum-Combobox-input",
6271
".spectrum-Combobox:has(:active) .spectrum-Combobox-button.is-invalid:not(:disabled, .spectrum-PickerButton--quiet)",
6372
".spectrum-Combobox:has(:active) .spectrum-Combobox-button:not(:disabled, .is-invalid, .spectrum-PickerButton--quiet)",
6473
".spectrum-Combobox:has(:focus) .spectrum-Combobox-button.is-invalid:not(:disabled, .spectrum-PickerButton--quiet)",
@@ -116,6 +125,7 @@
116125
"--mod-combobox-inline-size",
117126
"--mod-combobox-line-height",
118127
"--mod-combobox-min-inline-size",
128+
"--mod-combobox-readonly-input-border-color",
119129
"--mod-combobox-spacing-block-end-edge-to-text",
120130
"--mod-combobox-spacing-block-start-edge-to-text",
121131
"--mod-combobox-spacing-edge-to-menu",
@@ -136,6 +146,7 @@
136146
"--spectrum-combobox-block-spacing-edge-to-alert",
137147
"--spectrum-combobox-block-spacing-edge-to-progress-circle",
138148
"--spectrum-combobox-border-color-default",
149+
"--spectrum-combobox-border-color-disabled",
139150
"--spectrum-combobox-border-color-focus",
140151
"--spectrum-combobox-border-color-focus-hover",
141152
"--spectrum-combobox-border-color-hover",
@@ -158,6 +169,11 @@
158169
"--spectrum-combobox-inline-size",
159170
"--spectrum-combobox-line-height",
160171
"--spectrum-combobox-min-inline-size",
172+
"--spectrum-combobox-readonly-background-color-disabled",
173+
"--spectrum-combobox-readonly-border-color-invalid-default",
174+
"--spectrum-combobox-readonly-input-background-color",
175+
"--spectrum-combobox-readonly-input-border-color",
176+
"--spectrum-combobox-readonly-text-color-disabled",
161177
"--spectrum-combobox-spacing-block-end-edge-to-text",
162178
"--spectrum-combobox-spacing-block-start-edge-to-text",
163179
"--spectrum-combobox-spacing-edge-to-menu",
@@ -190,6 +206,9 @@
190206
"--spectrum-component-top-to-text-75",
191207
"--spectrum-corner-radius-100",
192208
"--spectrum-default-font-style",
209+
"--spectrum-disabled-background-color",
210+
"--spectrum-disabled-border-color",
211+
"--spectrum-disabled-content-color",
193212
"--spectrum-field-edge-to-text-quiet",
194213
"--spectrum-field-label-to-component",
195214
"--spectrum-field-label-to-component-quiet-extra-large",
@@ -213,6 +232,7 @@
213232
"--spectrum-font-size-300",
214233
"--spectrum-font-size-75",
215234
"--spectrum-gray-400",
235+
"--spectrum-gray-50",
216236
"--spectrum-gray-500",
217237
"--spectrum-gray-600",
218238
"--spectrum-gray-800",
@@ -272,6 +292,8 @@
272292
],
273293
"high-contrast": [
274294
"--highcontrast-combobox-border-color-highlight",
275-
"--highcontrast-combobox-border-color-invalid"
295+
"--highcontrast-combobox-border-color-invalid",
296+
"--highcontrast-textfield-border-color-invalid-default",
297+
"--highcontrast-textfield-text-color-disabled"
276298
]
277299
}

components/combobox/metadata/mods.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
| `--mod-combobox-inline-size` |
4444
| `--mod-combobox-line-height` |
4545
| `--mod-combobox-min-inline-size` |
46+
| `--mod-combobox-readonly-input-border-color` |
4647
| `--mod-combobox-spacing-block-end-edge-to-text` |
4748
| `--mod-combobox-spacing-block-start-edge-to-text` |
4849
| `--mod-combobox-spacing-edge-to-menu` |

components/combobox/stories/combobox.stories.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Template as Menu } from "@spectrum-css/menu/stories/template.js";
22
import { disableDefaultModes } from "@spectrum-css/preview/modes";
3-
import { isDisabled, isFocused, isInvalid, isKeyboardFocused, isLoading, isOpen, isQuiet, size } from "@spectrum-css/preview/types";
3+
import { isDisabled, isFocused, isInvalid, isKeyboardFocused, isLoading, isOpen, isQuiet, isReadOnly, size } from "@spectrum-css/preview/types";
44
import metadata from "../metadata/metadata.json";
55
import packageJson from "../package.json";
66
import { ComboBoxGroup } from "./combobox.test.js";
7-
import { VariantGroup } from "./template.js";
7+
import { Template, VariantGroup } from "./template.js";
88

99
/**
1010
* Comboboxes combine a text entry with a picker menu, allowing users to filter longer lists to only the selections matching a query.
@@ -18,7 +18,7 @@ import { VariantGroup } from "./template.js";
1818
* - `.spectrum-Combobox-textfield` is required on the Textfield outer element (`.spectrum-Textfield`)
1919
* - `.spectrum-Combobox-input` is required on the `<input>` element inside of Textfields (`.spectrum-Textfield-input`)
2020
* - `.spectrum-Combobox-button` is required on the FieldButton (`.spectrum-ActionButton spectrum-ActionButton--sizeM`)
21-
*
21+
*
2222
* ### Indicating validity and focus
2323
*
2424
* Validity and focus must be bubbled up to the parent so descendants siblings can be styled. Implementations should add the following classes to the `.spectrum-Combobox` parent class in the following situations:
@@ -40,13 +40,17 @@ export default {
4040
component: "Combobox",
4141
argTypes: {
4242
size: size(["s", "m", "l", "xl"]),
43-
isOpen,
43+
isOpen: {
44+
...isOpen,
45+
if: { arg: "isReadOnly", truthy: false },
46+
},
4447
isQuiet,
4548
isInvalid,
4649
isFocused,
4750
isKeyboardFocused,
4851
isLoading,
4952
isDisabled,
53+
isReadOnly,
5054
showFieldLabel: {
5155
name: "Show field label",
5256
type: { name: "boolean" },
@@ -99,6 +103,7 @@ export default {
99103
isKeyboardFocused: false,
100104
isLoading: false,
101105
isDisabled: false,
106+
isReadOnly: false,
102107
showFieldLabel: false,
103108
testId: "combobox",
104109
content: [
@@ -165,6 +170,21 @@ QuietGroup.parameters = {
165170
chromatic: { disableSnapshot: true },
166171
};
167172

173+
/**
174+
* Comboboxes have a read-only option for when content in the disabled state still needs to be shown. This allows for content to be copied, but not interacted with or changed. A combobox does not have a read-only option if no selection has been made. To enable this feature, add the `.isReadOnly` class to the combobox. To enable this feature, add the .isReadOnly class to the combobox. Then within the nested textfield component, add the .isReadOnly class and readonly attribute to the `<input>` element.
175+
*/
176+
export const ReadOnly = Template.bind({});
177+
ReadOnly.tags = ["!dev"];
178+
ReadOnly.args = {
179+
isReadOnly: true,
180+
value: "Ballard"
181+
};
182+
ReadOnly.parameters = {
183+
chromatic: { disableSnapshot: true }
184+
};
185+
186+
ReadOnly.storyName = "Read-only";
187+
168188
// ********* VRT ONLY ********* //
169189
export const WithForcedColors = ComboBoxGroup.bind({});
170190
WithForcedColors.tags = ["!autodocs", "!dev"];

components/combobox/stories/combobox.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const ComboBoxGroup = Variants({
4848
isOpen: false,
4949
isQuiet: true,
5050
value: "United States of America and to the republic",
51-
}
51+
},
5252
],
5353
stateData: [
5454
{
@@ -71,5 +71,9 @@ export const ComboBoxGroup = Variants({
7171
testHeading: "Loading",
7272
isLoading: true,
7373
},
74+
{
75+
testHeading: "Read-only",
76+
isReadOnly: true
77+
},
7478
],
7579
});

components/combobox/stories/template.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const Combobox = ({
2525
isFocused = false,
2626
isKeyboardFocused = false,
2727
isLoading = false,
28+
isReadOnly = false,
2829
value = "",
2930
} = {}, context = {}) => {
3031
const { updateArgs } = context;
@@ -42,6 +43,7 @@ const Combobox = ({
4243
"is-keyboardFocused": !isDisabled && isKeyboardFocused,
4344
"is-loading": isLoading,
4445
"is-disabled": isDisabled,
46+
"is-readOnly": isReadOnly,
4547
...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}),
4648
})}
4749
id=${ifDefined(id)}
@@ -63,6 +65,7 @@ const Combobox = ({
6365
isLoading,
6466
customProgressCircleClasses: ["spectrum-Combobox-progress-circle"],
6567
name: "field",
68+
isReadOnly,
6669
value,
6770
onclick: function () {
6871
if (!isOpen) updateArgs({ isOpen: true });
@@ -97,6 +100,7 @@ export const Template = ({
97100
isQuiet = false,
98101
isDisabled = false,
99102
showFieldLabel = false,
103+
isReadOnly = false,
100104
fieldLabelText = "Select location",
101105
fieldLabelPosition = "top",
102106
content = [],
@@ -108,19 +112,20 @@ export const Template = ({
108112
<div style=${styleMap({
109113
// This accounts for the height of the popover when it is open to prevent testing issues
110114
// and allow docs containers to be the right height
111-
["margin-block-end"]: isOpen && !isDisabled ? `${popoverHeight}px` : undefined,
115+
["margin-block-end"]: !isReadOnly && isOpen && !isDisabled ? `${popoverHeight}px` : undefined,
112116
})}>
113117
${when(showFieldLabel, () =>
114118
FieldLabel({
115119
size,
116120
label: fieldLabelText,
121+
isDisabled,
117122
customStyles: { "max-inline-size": "100px"},
118123
alignment: fieldLabelPosition === "left" && "left",
119124
}, context)
120125
)}
121126
${[
122127
Popover({
123-
isOpen: isOpen && !isDisabled,
128+
isOpen: isOpen && !isDisabled && !isReadOnly,
124129
withTip: false,
125130
position: "bottom-start",
126131
isQuiet,
@@ -129,6 +134,7 @@ export const Template = ({
129134
isOpen,
130135
isQuiet,
131136
isDisabled,
137+
isReadOnly,
132138
value,
133139
...args,
134140
...passthrough,

0 commit comments

Comments
 (0)