Skip to content

Commit 25a4778

Browse files
authored
feat(combobox, combobox-item): add description, shortHeading props and content-end slot (#9771)
**Related Issue:** #3695 ## Summary This adds the following enhancements to `combobox`/`combobox-item`: * `description` prop - displays description below label * `shortHeading` prop - displays short version of the heading (label) in selection * `content-end` slot - enables slotting non-interactive elements after the item's content **Note**: the new props are filterable and also participate in visual matching
1 parent 5a6a68f commit 25a4778

File tree

7 files changed

+238
-55
lines changed

7 files changed

+238
-55
lines changed

packages/calcite-components/src/components.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,10 @@ export namespace Components {
12671267
* Specifies the parent and grandparent items, which are set on `calcite-combobox`.
12681268
*/
12691269
"ancestors": ComboboxChildElement[];
1270+
/**
1271+
* A description for the component, which displays below the label.
1272+
*/
1273+
"description": string;
12701274
/**
12711275
* When `true`, interaction is prevented and the component is displayed with lower opacity.
12721276
*/
@@ -1306,6 +1310,10 @@ export namespace Components {
13061310
"single" | "single-persist" | "ancestors" | "multiple",
13071311
SelectionMode
13081312
>;
1313+
/**
1314+
* The component's short heading. When provided, the short heading will be displayed in the component's selection. It is recommended to use 5 characters or fewer.
1315+
*/
1316+
"shortHeading": string;
13091317
/**
13101318
* The component's text.
13111319
*/
@@ -9110,6 +9118,10 @@ declare namespace LocalJSX {
91109118
* Specifies the parent and grandparent items, which are set on `calcite-combobox`.
91119119
*/
91129120
"ancestors"?: ComboboxChildElement[];
9121+
/**
9122+
* A description for the component, which displays below the label.
9123+
*/
9124+
"description"?: string;
91139125
/**
91149126
* When `true`, interaction is prevented and the component is displayed with lower opacity.
91159127
*/
@@ -9153,6 +9165,10 @@ declare namespace LocalJSX {
91539165
"single" | "single-persist" | "ancestors" | "multiple",
91549166
SelectionMode
91559167
>;
9168+
/**
9169+
* The component's short heading. When provided, the short heading will be displayed in the component's selection. It is recommended to use 5 characters or fewer.
9170+
*/
9171+
"shortHeading"?: string;
91569172
/**
91579173
* The component's text.
91589174
*/

packages/calcite-components/src/components/combobox-item/combobox-item.scss

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
--calcite-combobox-item-spacing-unit-s: theme("spacing.1");
77
--calcite-combobox-item-spacing-indent: theme("spacing.2");
88
--calcite-combobox-item-selector-icon-size: theme("spacing.4");
9+
--calcite-combobox-item-description-font-size: var(--calcite-font-size-xs);
910
}
1011

1112
.scale--m {
@@ -14,6 +15,7 @@
1415
--calcite-combobox-item-spacing-unit-s: theme("spacing.2");
1516
--calcite-combobox-item-spacing-indent: theme("spacing.3");
1617
--calcite-combobox-item-selector-icon-size: theme("spacing.4");
18+
--calcite-combobox-item-description-font-size: var(--calcite-font-size-sm);
1719
}
1820

1921
.scale--l {
@@ -22,6 +24,7 @@
2224
--calcite-combobox-item-spacing-unit-s: theme("spacing[2.5]");
2325
--calcite-combobox-item-spacing-indent: theme("spacing.4");
2426
--calcite-combobox-item-selector-icon-size: theme("spacing.6");
27+
--calcite-combobox-item-description-font-size: var(--calcite-font-size);
2528
}
2629

2730
.container {
@@ -48,20 +51,22 @@ ul:focus {
4851

4952
.label {
5053
@apply text-color-3
51-
focus-base
52-
relative
53-
box-border
54-
flex
55-
w-full
56-
min-w-full
57-
cursor-pointer
58-
items-center
59-
no-underline
60-
duration-150
61-
ease-in-out;
54+
focus-base
55+
relative
56+
box-border
57+
flex
58+
w-full
59+
min-w-full
60+
cursor-pointer
61+
items-center
62+
no-underline
63+
duration-150
64+
ease-in-out;
6265
@include word-break();
66+
justify-content: space-around;
67+
gap: var(--calcite-combobox-item-spacing-unit-l);
6368
padding-block: var(--calcite-combobox-item-spacing-unit-s);
64-
padding-inline: var(--calcite-combobox-item-spacing-unit-l);
69+
padding-inline: var(--calcite-combobox-item-indent-value);
6570
}
6671

6772
:host([disabled]) .label {
@@ -85,11 +90,6 @@ ul:focus {
8590
shadow-none;
8691
}
8792

88-
.title {
89-
padding-block: 0;
90-
padding-inline: var(--calcite-combobox-item-spacing-unit-l);
91-
}
92-
9393
.icon {
9494
@apply inline-flex
9595
opacity-0
@@ -98,13 +98,8 @@ ul:focus {
9898
color: theme("borderColor.color.1");
9999
}
100100

101-
.icon--indent {
102-
padding-inline-start: var(--calcite-combobox-item-indent-value);
103-
}
104-
105101
.icon--custom {
106102
margin-block-start: -1px;
107-
padding-inline-start: var(--calcite-combobox-item-spacing-unit-l);
108103
@apply text-color-3;
109104
}
110105

@@ -140,3 +135,26 @@ ul:focus {
140135
color: var(--calcite-color-text-1);
141136
background-color: var(--calcite-color-foreground-current);
142137
}
138+
139+
.center-content {
140+
display: flex;
141+
flex-direction: column;
142+
flex-grow: 1;
143+
padding-block: 0;
144+
}
145+
146+
.description {
147+
font-size: var(--calcite-combobox-item-description-font-size);
148+
font-weight: var(--calcite-font-weight-normal);
149+
}
150+
151+
:host([selected]),
152+
:host(:hover) {
153+
.description {
154+
color: var(--calcite-color-text-2);
155+
}
156+
}
157+
158+
.short-text {
159+
color: var(--calcite-color-text-3);
160+
}

packages/calcite-components/src/components/combobox-item/combobox-item.tsx

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import { getAncestors, getDepth, isSingleLike } from "../combobox/utils";
2828
import { Scale, SelectionMode } from "../interfaces";
2929
import { getIconScale } from "../../utils/component";
3030
import { IconName } from "../icon/interfaces";
31-
import { CSS } from "./resources";
31+
import { CSS, SLOTS } from "./resources";
3232

3333
/**
3434
* @slot - A slot for adding nested `calcite-combobox-item`s.
35+
* @slot content-end - A slot for adding non-actionable elements after the component's content.
3536
*/
3637
@Component({
3738
tag: "calcite-combobox-item",
@@ -59,6 +60,11 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
5960
/** Specifies the parent and grandparent items, which are set on `calcite-combobox`. */
6061
@Prop({ mutable: true }) ancestors: ComboboxChildElement[];
6162

63+
/**
64+
* A description for the component, which displays below the label.
65+
*/
66+
@Prop() description: string;
67+
6268
/** The `id` attribute of the component. When omitted, a globally unique identifier is used. */
6369
@Prop({ reflect: true }) guid = guid();
6470

@@ -83,14 +89,18 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
8389
*/
8490
@Prop({ reflect: true }) filterTextMatchPattern: RegExp;
8591

86-
/** The component's value. */
87-
@Prop() value!: any;
88-
8992
/**
9093
* When `true`, omits the component from the `calcite-combobox` filtered search results.
9194
*/
9295
@Prop({ reflect: true }) filterDisabled: boolean;
9396

97+
/**
98+
* Specifies the size of the component inherited from the `calcite-combobox`, defaults to `m`.
99+
*
100+
* @internal
101+
*/
102+
@Prop() scale: Scale = "m";
103+
94104
/**
95105
* Specifies the selection mode of the component, where:
96106
*
@@ -110,11 +120,16 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
110120
> = "multiple";
111121

112122
/**
113-
* Specifies the size of the component inherited from the `calcite-combobox`, defaults to `m`.
123+
* The component's short heading.
114124
*
115-
* @internal
125+
* When provided, the short heading will be displayed in the component's selection.
126+
*
127+
* It is recommended to use 5 characters or fewer.
116128
*/
117-
@Prop() scale: Scale = "m";
129+
@Prop({ reflect: true }) shortHeading: string;
130+
131+
/** The component's value. */
132+
@Prop() value!: any;
118133

119134
// --------------------------------------------------------------------------
120135
//
@@ -189,7 +204,6 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
189204
class={{
190205
[CSS.custom]: !!this.icon,
191206
[CSS.iconActive]: this.icon && this.selected,
192-
[CSS.iconIndent]: true,
193207
}}
194208
flipRtl={this.iconFlipRtl}
195209
icon={this.icon || iconPath}
@@ -207,15 +221,13 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
207221
class={{
208222
[CSS.icon]: true,
209223
[CSS.dot]: true,
210-
[CSS.iconIndent]: true,
211224
}}
212225
/>
213226
) : (
214227
<calcite-icon
215228
class={{
216229
[CSS.icon]: true,
217230
[CSS.iconActive]: this.selected,
218-
[CSS.iconIndent]: true,
219231
}}
220232
flipRtl={this.iconFlipRtl}
221233
icon={iconPath}
@@ -250,19 +262,31 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
250262
[CSS.active]: this.active,
251263
[CSS.single]: isSingleSelect,
252264
};
253-
const depth = getDepth(this.el);
265+
const depth = getDepth(this.el) + 1;
254266

255267
return (
256268
<Host aria-hidden="true">
257269
<InteractiveContainer disabled={disabled}>
258270
<div
259-
class={`container scale--${this.scale}`}
271+
class={{
272+
[CSS.container]: true,
273+
[CSS.scale(this.scale)]: true,
274+
}}
260275
style={{ "--calcite-combobox-item-spacing-indent-multiplier": `${depth}` }}
261276
>
262277
<li class={classes} id={this.guid} onClick={this.itemClickHandler}>
263278
{this.renderSelectIndicator(showDot, iconPath)}
264279
{this.renderIcon(iconPath)}
265-
<span class="title">{this.renderTextContent()}</span>
280+
<div class={CSS.centerContent}>
281+
<div class={CSS.title}>{this.renderTextContent(this.textLabel)}</div>
282+
{this.description ? (
283+
<div class={CSS.description}>{this.renderTextContent(this.description)}</div>
284+
) : null}
285+
</div>
286+
{this.shortHeading ? (
287+
<div class={CSS.shortText}>{this.renderTextContent(this.shortHeading)}</div>
288+
) : null}
289+
<slot name={SLOTS.contentEnd} />
266290
</li>
267291
{this.renderChildren()}
268292
</div>
@@ -271,12 +295,14 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon
271295
);
272296
}
273297

274-
private renderTextContent(): string | (string | VNode)[] {
275-
if (!this.filterTextMatchPattern) {
276-
return this.textLabel;
298+
private renderTextContent(text: string): string | (string | VNode)[] {
299+
const pattern = this.filterTextMatchPattern;
300+
301+
if (!pattern || !text) {
302+
return text;
277303
}
278304

279-
const parts: (string | VNode)[] = this.textLabel.split(this.filterTextMatchPattern);
305+
const parts: (string | VNode)[] = text.split(pattern);
280306

281307
if (parts.length > 1) {
282308
// we only highlight the first match
Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1+
import { Scale } from "../interfaces";
2+
13
export const CSS = {
2-
icon: "icon",
3-
iconActive: "icon--active",
4-
iconIndent: "icon--indent",
4+
active: "label--active",
5+
centerContent: "center-content",
6+
container: "container",
57
custom: "icon--custom",
8+
description: "description",
69
dot: "icon--dot",
7-
single: "label--single",
10+
filterMatch: "filter-match",
11+
icon: "icon",
12+
iconActive: "icon--active",
813
label: "label",
9-
active: "label--active",
14+
scale: (scale: Scale) => `scale--${scale}` as const,
1015
selected: "label--selected",
11-
title: "title",
16+
shortText: "short-text",
17+
single: "label--single",
1218
textContainer: "text-container",
13-
filterMatch: "filter-match",
19+
title: "title",
20+
};
21+
22+
export const SLOTS = {
23+
contentEnd: "content-end",
1424
};

0 commit comments

Comments
 (0)