From 25a4778cf7ebbdcfbe6f7da7e0c86238e7cbf819 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jul 2024 11:37:19 -0700 Subject: [PATCH] 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 --- .../calcite-components/src/components.d.ts | 16 +++ .../combobox-item/combobox-item.scss | 62 +++++++---- .../combobox-item/combobox-item.tsx | 60 ++++++++--- .../src/components/combobox-item/resources.ts | 24 +++-- .../components/combobox/combobox.stories.ts | 100 +++++++++++++++++- .../src/components/combobox/combobox.tsx | 27 +++-- .../src/components/combobox/utils.ts | 4 + 7 files changed, 238 insertions(+), 55 deletions(-) diff --git a/packages/calcite-components/src/components.d.ts b/packages/calcite-components/src/components.d.ts index 276991b089b..7a7c23f9af6 100644 --- a/packages/calcite-components/src/components.d.ts +++ b/packages/calcite-components/src/components.d.ts @@ -1267,6 +1267,10 @@ export namespace Components { * Specifies the parent and grandparent items, which are set on `calcite-combobox`. */ "ancestors": ComboboxChildElement[]; + /** + * A description for the component, which displays below the label. + */ + "description": string; /** * When `true`, interaction is prevented and the component is displayed with lower opacity. */ @@ -1306,6 +1310,10 @@ export namespace Components { "single" | "single-persist" | "ancestors" | "multiple", SelectionMode >; + /** + * 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. + */ + "shortHeading": string; /** * The component's text. */ @@ -9110,6 +9118,10 @@ declare namespace LocalJSX { * Specifies the parent and grandparent items, which are set on `calcite-combobox`. */ "ancestors"?: ComboboxChildElement[]; + /** + * A description for the component, which displays below the label. + */ + "description"?: string; /** * When `true`, interaction is prevented and the component is displayed with lower opacity. */ @@ -9153,6 +9165,10 @@ declare namespace LocalJSX { "single" | "single-persist" | "ancestors" | "multiple", SelectionMode >; + /** + * 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. + */ + "shortHeading"?: string; /** * The component's text. */ diff --git a/packages/calcite-components/src/components/combobox-item/combobox-item.scss b/packages/calcite-components/src/components/combobox-item/combobox-item.scss index 9c10946cbdd..c06009ed955 100644 --- a/packages/calcite-components/src/components/combobox-item/combobox-item.scss +++ b/packages/calcite-components/src/components/combobox-item/combobox-item.scss @@ -6,6 +6,7 @@ --calcite-combobox-item-spacing-unit-s: theme("spacing.1"); --calcite-combobox-item-spacing-indent: theme("spacing.2"); --calcite-combobox-item-selector-icon-size: theme("spacing.4"); + --calcite-combobox-item-description-font-size: var(--calcite-font-size-xs); } .scale--m { @@ -14,6 +15,7 @@ --calcite-combobox-item-spacing-unit-s: theme("spacing.2"); --calcite-combobox-item-spacing-indent: theme("spacing.3"); --calcite-combobox-item-selector-icon-size: theme("spacing.4"); + --calcite-combobox-item-description-font-size: var(--calcite-font-size-sm); } .scale--l { @@ -22,6 +24,7 @@ --calcite-combobox-item-spacing-unit-s: theme("spacing[2.5]"); --calcite-combobox-item-spacing-indent: theme("spacing.4"); --calcite-combobox-item-selector-icon-size: theme("spacing.6"); + --calcite-combobox-item-description-font-size: var(--calcite-font-size); } .container { @@ -48,20 +51,22 @@ ul:focus { .label { @apply text-color-3 - focus-base - relative - box-border - flex - w-full - min-w-full - cursor-pointer - items-center - no-underline - duration-150 - ease-in-out; + focus-base + relative + box-border + flex + w-full + min-w-full + cursor-pointer + items-center + no-underline + duration-150 + ease-in-out; @include word-break(); + justify-content: space-around; + gap: var(--calcite-combobox-item-spacing-unit-l); padding-block: var(--calcite-combobox-item-spacing-unit-s); - padding-inline: var(--calcite-combobox-item-spacing-unit-l); + padding-inline: var(--calcite-combobox-item-indent-value); } :host([disabled]) .label { @@ -85,11 +90,6 @@ ul:focus { shadow-none; } -.title { - padding-block: 0; - padding-inline: var(--calcite-combobox-item-spacing-unit-l); -} - .icon { @apply inline-flex opacity-0 @@ -98,13 +98,8 @@ ul:focus { color: theme("borderColor.color.1"); } -.icon--indent { - padding-inline-start: var(--calcite-combobox-item-indent-value); -} - .icon--custom { margin-block-start: -1px; - padding-inline-start: var(--calcite-combobox-item-spacing-unit-l); @apply text-color-3; } @@ -140,3 +135,26 @@ ul:focus { color: var(--calcite-color-text-1); background-color: var(--calcite-color-foreground-current); } + +.center-content { + display: flex; + flex-direction: column; + flex-grow: 1; + padding-block: 0; +} + +.description { + font-size: var(--calcite-combobox-item-description-font-size); + font-weight: var(--calcite-font-weight-normal); +} + +:host([selected]), +:host(:hover) { + .description { + color: var(--calcite-color-text-2); + } +} + +.short-text { + color: var(--calcite-color-text-3); +} diff --git a/packages/calcite-components/src/components/combobox-item/combobox-item.tsx b/packages/calcite-components/src/components/combobox-item/combobox-item.tsx index f65d4d4fb19..b908cbd4855 100644 --- a/packages/calcite-components/src/components/combobox-item/combobox-item.tsx +++ b/packages/calcite-components/src/components/combobox-item/combobox-item.tsx @@ -28,10 +28,11 @@ import { getAncestors, getDepth, isSingleLike } from "../combobox/utils"; import { Scale, SelectionMode } from "../interfaces"; import { getIconScale } from "../../utils/component"; import { IconName } from "../icon/interfaces"; -import { CSS } from "./resources"; +import { CSS, SLOTS } from "./resources"; /** * @slot - A slot for adding nested `calcite-combobox-item`s. + * @slot content-end - A slot for adding non-actionable elements after the component's content. */ @Component({ tag: "calcite-combobox-item", @@ -59,6 +60,11 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon /** Specifies the parent and grandparent items, which are set on `calcite-combobox`. */ @Prop({ mutable: true }) ancestors: ComboboxChildElement[]; + /** + * A description for the component, which displays below the label. + */ + @Prop() description: string; + /** The `id` attribute of the component. When omitted, a globally unique identifier is used. */ @Prop({ reflect: true }) guid = guid(); @@ -83,14 +89,18 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon */ @Prop({ reflect: true }) filterTextMatchPattern: RegExp; - /** The component's value. */ - @Prop() value!: any; - /** * When `true`, omits the component from the `calcite-combobox` filtered search results. */ @Prop({ reflect: true }) filterDisabled: boolean; + /** + * Specifies the size of the component inherited from the `calcite-combobox`, defaults to `m`. + * + * @internal + */ + @Prop() scale: Scale = "m"; + /** * Specifies the selection mode of the component, where: * @@ -110,11 +120,16 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon > = "multiple"; /** - * Specifies the size of the component inherited from the `calcite-combobox`, defaults to `m`. + * The component's short heading. * - * @internal + * When provided, the short heading will be displayed in the component's selection. + * + * It is recommended to use 5 characters or fewer. */ - @Prop() scale: Scale = "m"; + @Prop({ reflect: true }) shortHeading: string; + + /** The component's value. */ + @Prop() value!: any; // -------------------------------------------------------------------------- // @@ -189,7 +204,6 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon class={{ [CSS.custom]: !!this.icon, [CSS.iconActive]: this.icon && this.selected, - [CSS.iconIndent]: true, }} flipRtl={this.iconFlipRtl} icon={this.icon || iconPath} @@ -207,7 +221,6 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon class={{ [CSS.icon]: true, [CSS.dot]: true, - [CSS.iconIndent]: true, }} /> ) : ( @@ -215,7 +228,6 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon class={{ [CSS.icon]: true, [CSS.iconActive]: this.selected, - [CSS.iconIndent]: true, }} flipRtl={this.iconFlipRtl} icon={iconPath} @@ -250,19 +262,31 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon [CSS.active]: this.active, [CSS.single]: isSingleSelect, }; - const depth = getDepth(this.el); + const depth = getDepth(this.el) + 1; return (