diff --git a/packages/calcite-components/src/assets/styles/includes.scss b/packages/calcite-components/src/assets/styles/includes.scss index 70f265e2100..7c5d7d2b548 100644 --- a/packages/calcite-components/src/assets/styles/includes.scss +++ b/packages/calcite-components/src/assets/styles/includes.scss @@ -36,6 +36,81 @@ } } +// mixin for the container of label displayed with form-associated components +@mixin form-internal-label() { + .internal-label-alignment--center { + align-items: center; + } + + .internal-label-alignment--end { + align-items: end; + } + + .internal-label--container { + display: flex; + justify-content: space-between; + color: var(--calcite-color-text-1); + } + + .internal-label-required--indicator { + font-weight: var(--calcite-font-weight-medium); + color: var(--calcite-color-status-danger); + padding-inline: var(--calcite-spacing-base); + &:hover { + cursor: help; + } + } + + .internal-label--text { + line-height: 1; + } + + :host([scale="s"]) { + .internal-label-spacing--bottom { + margin-block-end: var(--calcite-spacing-xxs); + } + .internal-label-spacing-inline--end { + margin-inline-end: var(--calcite-spacing-sm); + } + .internal-label-spacing-inline--start { + margin-inline-start: var(--calcite-spacing-sm); + } + .internal-label--text { + font-size: var(--calcite-font-size--2); + } + } + + :host([scale="m"]) { + .internal-label-spacing--bottom { + margin-block-end: var(--calcite-spacing-sm); + } + .internal-label-spacing-inline--end { + margin-inline-end: var(--calcite-spacing-sm); + } + .internal-label-spacing-inline--start { + margin-inline-start: var(--calcite-spacing-sm); + } + .internal-label--text { + font-size: var(--calcite-font-size--1); + } + } + + :host([scale="l"]) { + .internal-label-spacing--bottom { + margin-block-end: var(--calcite-spacing-sm); + } + .internal-label-spacing-inline--end { + margin-inline-end: var(--calcite-spacing-md); + } + .internal-label-spacing-inline--start { + margin-inline-start: var(--calcite-spacing-md); + } + .internal-label--text { + font-size: var(--calcite-font-size-0); + } + } +} + // mixin for the container of validation messages displayed below form-associated components @mixin form-validation-message() { .validation-container { diff --git a/packages/calcite-components/src/components/autocomplete/autocomplete.e2e.ts b/packages/calcite-components/src/components/autocomplete/autocomplete.e2e.ts index ce4ef428daf..e50bf446747 100644 --- a/packages/calcite-components/src/components/autocomplete/autocomplete.e2e.ts +++ b/packages/calcite-components/src/components/autocomplete/autocomplete.e2e.ts @@ -8,6 +8,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, openClose, reflects, @@ -478,6 +479,10 @@ describe("calcite-autocomplete", () => { focusable("calcite-autocomplete"); }); + describe("InternalLabel", () => { + internalLabel(`calcite-autocomplete`); + }); + it("should set screen reader list attribute 'aria-live' to 'polite'", async () => { const page = await newE2EPage(); await page.setContent(simpleHTML); diff --git a/packages/calcite-components/src/components/autocomplete/autocomplete.scss b/packages/calcite-components/src/components/autocomplete/autocomplete.scss index 1de42e35968..65722aaeacd 100644 --- a/packages/calcite-components/src/components/autocomplete/autocomplete.scss +++ b/packages/calcite-components/src/components/autocomplete/autocomplete.scss @@ -117,6 +117,7 @@ @apply sr-only; } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/autocomplete/autocomplete.tsx b/packages/calcite-components/src/components/autocomplete/autocomplete.tsx index aa2307cd849..d433ce69f2e 100644 --- a/packages/calcite-components/src/components/autocomplete/autocomplete.tsx +++ b/packages/calcite-components/src/components/autocomplete/autocomplete.tsx @@ -34,7 +34,7 @@ import { import { toggleOpenClose, OpenCloseComponent } from "../../utils/openCloseComponent"; import { Alignment, Scale, Status } from "../interfaces"; import { IconNameOrString } from "../icon/interfaces"; -import { connectLabel, disconnectLabel, LabelableComponent } from "../../utils/label"; +import { connectLabel, disconnectLabel, LabelableComponent, getLabelText } from "../../utils/label"; import { TextualInputComponent } from "../input/common/input"; import { afterConnectDefaultValueSet, @@ -53,6 +53,7 @@ import type { Input } from "../input/input"; import type { AutocompleteItem } from "../autocomplete-item/autocomplete-item"; import type { AutocompleteItemGroup } from "../autocomplete-item-group/autocomplete-item-group"; import type { Label } from "../label/label"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { createObserver } from "../../utils/observers"; import { useSetFocus } from "../../controllers/useSetFocus"; @@ -73,6 +74,7 @@ declare global { * @slot - A slot for adding `calcite-autocomplete-item` elements. * @slot content-bottom - A slot for adding content below `calcite-autocomplete-item` elements. * @slot content-top - A slot for adding content above `calcite-autocomplete-item` elements. + * @slot label-content - A slot for rendering content next to the component's `labelText`. */ export class Autocomplete extends LitElement @@ -206,6 +208,9 @@ export class Autocomplete /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** When present, a busy indicator is displayed. */ @property({ reflect: true }) loading = false; @@ -781,11 +786,20 @@ export class Autocomplete return ( + {this.labelText && ( + + )}
html` `; - diff --git a/packages/calcite-components/src/components/checkbox/checkbox.e2e.ts b/packages/calcite-components/src/components/checkbox/checkbox.e2e.ts index ec1856fad8e..16bc81e08da 100644 --- a/packages/calcite-components/src/components/checkbox/checkbox.e2e.ts +++ b/packages/calcite-components/src/components/checkbox/checkbox.e2e.ts @@ -7,8 +7,10 @@ import { focusable, formAssociated, hidden, + internalLabel, HYDRATED_ATTR, labelable, + t9n, themed, } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; @@ -184,6 +186,10 @@ describe("calcite-checkbox", () => { }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-checkbox`); + }); + describe("WCAG AA recommended minimum 24px click area", () => { const testCheckboxClick = async (scale: Scale, maxExtraPixels: number, direction: "ltr" | "rtl"): Promise => { const page = await newE2EPage(); @@ -225,6 +231,10 @@ describe("calcite-checkbox", () => { }); }); + describe("translation support", () => { + t9n("calcite-checkbox"); + }); + describe("theme", () => { describe("default", () => { themed(html` `, { diff --git a/packages/calcite-components/src/components/checkbox/checkbox.scss b/packages/calcite-components/src/components/checkbox/checkbox.scss index 6ed3331a1de..48f9f4145e3 100644 --- a/packages/calcite-components/src/components/checkbox/checkbox.scss +++ b/packages/calcite-components/src/components/checkbox/checkbox.scss @@ -146,6 +146,7 @@ } } +@include form-internal-label(); @include disabled(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/checkbox/checkbox.tsx b/packages/calcite-components/src/components/checkbox/checkbox.tsx index 5d9f1ad4c8a..f50b3d8e853 100644 --- a/packages/calcite-components/src/components/checkbox/checkbox.tsx +++ b/packages/calcite-components/src/components/checkbox/checkbox.tsx @@ -19,9 +19,12 @@ import { connectLabel, disconnectLabel, getLabelText, LabelableComponent } from import { Scale, Status } from "../interfaces"; import { CSS_UTILITY } from "../../utils/resources"; import type { Label } from "../label/label"; +import { InternalLabel } from "../functional/InternalLabel"; +import { useT9n } from "../../controllers/useT9n"; import { useSetFocus } from "../../controllers/useSetFocus"; import { CSS } from "./resources"; import { styles } from "./checkbox.scss"; +import T9nStrings from "./assets/t9n/messages.en.json"; declare global { interface DeclareElements { @@ -59,6 +62,13 @@ export class Checkbox private toggleEl = createRef(); + /** + * Made into a prop for testing purposes only + * + * @private + */ + messages = useT9n(); + private focusSetter = useSetFocus()(this); // #endregion @@ -97,6 +107,12 @@ export class Checkbox /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + + /** Use this property to override individual strings used by the component. */ + @property() messageOverrides?: typeof this.messages._overrides; + /** * Specifies the name of the component. * @@ -254,6 +270,7 @@ export class Checkbox
+ {this.labelText && ( + + )} ); diff --git a/packages/calcite-components/src/components/combobox/combobox.e2e.ts b/packages/calcite-components/src/components/combobox/combobox.e2e.ts index 05ddf88d38e..64be329f110 100644 --- a/packages/calcite-components/src/components/combobox/combobox.e2e.ts +++ b/packages/calcite-components/src/components/combobox/combobox.e2e.ts @@ -9,6 +9,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, openClose, reflects, @@ -150,6 +151,10 @@ describe("calcite-combobox", () => { `); }); + describe("InternalLabel", () => { + internalLabel(`calcite-combobox`); + }); + describe("honors hidden attribute", () => { hidden("calcite-combobox"); }); diff --git a/packages/calcite-components/src/components/combobox/combobox.scss b/packages/calcite-components/src/components/combobox/combobox.scss index 4e5ded5e75e..dc238e36d8f 100644 --- a/packages/calcite-components/src/components/combobox/combobox.scss +++ b/packages/calcite-components/src/components/combobox/combobox.scss @@ -292,4 +292,5 @@ calcite-chip { padding-block-start: var(--calcite-internal-combobox-spacing-unit-l); } +@include form-internal-label(); @include input-placeholder-text(); diff --git a/packages/calcite-components/src/components/combobox/combobox.tsx b/packages/calcite-components/src/components/combobox/combobox.tsx index f076c6e4663..83e38f74034 100644 --- a/packages/calcite-components/src/components/combobox/combobox.tsx +++ b/packages/calcite-components/src/components/combobox/combobox.tsx @@ -51,6 +51,7 @@ import { DEBOUNCE } from "../../utils/resources"; import { Scale, SelectionMode, Status } from "../interfaces"; import { CSS as XButtonCSS, XButton } from "../functional/XButton"; import { getIconScale, isHidden } from "../../utils/component"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { IconNameOrString } from "../icon/interfaces"; import { useT9n } from "../../controllers/useT9n"; @@ -79,7 +80,10 @@ declare global { } } -/** @slot - A slot for adding `calcite-combobox-item`s. */ +/** + * @slot - A slot for adding `calcite-combobox-item`s. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class Combobox extends LitElement implements @@ -369,6 +373,9 @@ export class Combobox */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** Specifies the maximum number of `calcite-combobox-item`s (including nested children) to display before displaying a scrollbar. */ @property({ reflect: true }) maxItems = 0; @@ -1757,6 +1764,7 @@ export class Combobox placeholder={placeholder} readOnly={this.readOnly} ref={this.textInput} + required={this.required} role="combobox" tabIndex={this.activeChipIndex === -1 ? 0 : -1} type="text" @@ -1897,6 +1905,14 @@ export class Combobox return ( + {this.labelText && ( + + )}
void; + required?: boolean; + spacingInlineEnd?: boolean; + spacingInlineStart?: boolean; + tooltipText?: string; +} + +export const CSS = { + alignmentCenter: "internal-label-alignment--center", + alignmentEnd: "internal-label-alignment--end", + container: "internal-label--container", + requiredIndicator: "internal-label-required--indicator", + spacingBottom: "internal-label-spacing--bottom", + spacingInlineEnd: "internal-label-spacing-inline--end", + spacingInlineStart: "internal-label-spacing-inline--start", + text: "internal-label--text", +}; + +/** + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ + +export const InternalLabel = ({ + alignmentCenter, + bottomSpacingDisabled, + labelText, + onClick, + required, + spacingInlineEnd, + spacingInlineStart, + tooltipText, +}: InternalLabelProps): TemplateResult => ( +
+
+ {labelText} + {required && ( + + )} +
+ +
+); diff --git a/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts b/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts index d42d0d8bcb3..7614ebe311c 100644 --- a/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts +++ b/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts @@ -9,6 +9,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, openClose, renders, @@ -93,6 +94,10 @@ describe("calcite-input-date-picker", () => { }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-input-date-picker`); + }); + describe("event emitting when the value changes", () => { it("emits change event when value is committed for single date", async () => { const page = await newE2EPage(); diff --git a/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss b/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss index 0a922c064a1..f0f11f6ebd1 100644 --- a/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss +++ b/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss @@ -286,4 +286,5 @@ calcite-date-picker { @apply sr-only; } +@include form-internal-label(); @include base-component(); diff --git a/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx b/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx index 91a62efb182..758275c5abb 100644 --- a/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx +++ b/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx @@ -47,7 +47,7 @@ import { updateHostInteraction, } from "../../utils/interactive"; import { numberKeys } from "../../utils/key"; -import { connectLabel, disconnectLabel, LabelableComponent } from "../../utils/label"; +import { connectLabel, disconnectLabel, LabelableComponent, getLabelText } from "../../utils/label"; import { getIconScale } from "../../utils/component"; import { getDateFormatSupportedLocale, @@ -61,6 +61,7 @@ import { DateLocaleData, getLocaleData, getValueAsDateRange } from "../date-pick import { HeadingLevel } from "../functional/Heading"; import { guid } from "../../utils/guid"; import { Status } from "../interfaces"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { IconNameOrString } from "../icon/interfaces"; import { syncHiddenFormInput } from "../input/common/input"; @@ -81,6 +82,9 @@ declare global { } } +/** + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class InputDatePicker extends LitElement implements @@ -214,6 +218,9 @@ export class InputDatePicker /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** Defines the layout of the component. */ @property({ reflect: true }) layout: "horizontal" | "vertical" = "horizontal"; @@ -1095,8 +1102,21 @@ export class InputDatePicker return ( + {this.labelText && ( + + )}
-
+
{ }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-input-number`); + }); + describe.skip("increment/decrement functionality", () => { let page: E2EPage; beforeEach(async () => { diff --git a/packages/calcite-components/src/components/input-number/input-number.scss b/packages/calcite-components/src/components/input-number/input-number.scss index cae5da49748..a07eb249e0e 100755 --- a/packages/calcite-components/src/components/input-number/input-number.scss +++ b/packages/calcite-components/src/components/input-number/input-number.scss @@ -488,6 +488,7 @@ input { } } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/input-number/input-number.tsx b/packages/calcite-components/src/components/input-number/input-number.tsx index 6be5ec7f4cd..cb7c00257c6 100644 --- a/packages/calcite-components/src/components/input-number/input-number.tsx +++ b/packages/calcite-components/src/components/input-number/input-number.tsx @@ -42,6 +42,7 @@ import { import { CSS_UTILITY } from "../../utils/resources"; import { InputPlacement, NumberNudgeDirection, SetValueOrigin } from "../input/interfaces"; import { getIconScale } from "../../utils/component"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { NumericInputComponent, @@ -63,7 +64,10 @@ declare global { } } -/** @slot action - A slot for positioning a button next to the component. */ +/** + * @slot action - A slot for positioning a `calcite-action` or other interactive content. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class InputNumber extends LitElement implements @@ -202,6 +206,9 @@ export class InputNumber /** Accessible name for the component's button or hyperlink. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** When present, the component is in the loading state and `calcite-progress` is displayed. */ @property({ reflect: true }) loading = false; @@ -1036,6 +1043,7 @@ export class InputNumber placeholder={this.placeholder || ""} readOnly={this.readOnly} ref={this.setChildNumberElRef} + required={this.required} type="text" value={this.displayedValue} /> @@ -1043,6 +1051,14 @@ export class InputNumber return ( + {this.labelText && ( + + )}
{ }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-input-text`); + }); + it("does not fire any input or change events when a focused input is blurred after its value is set directly", async () => { const page = await newE2EPage({ html: "" }); const input = await page.find("calcite-input-text"); diff --git a/packages/calcite-components/src/components/input-text/input-text.scss b/packages/calcite-components/src/components/input-text/input-text.scss index 843a1839896..71fea9d81c5 100755 --- a/packages/calcite-components/src/components/input-text/input-text.scss +++ b/packages/calcite-components/src/components/input-text/input-text.scss @@ -421,6 +421,7 @@ input { } } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/input-text/input-text.tsx b/packages/calcite-components/src/components/input-text/input-text.tsx index 7820a44a6ba..3b9d2222a0d 100644 --- a/packages/calcite-components/src/components/input-text/input-text.tsx +++ b/packages/calcite-components/src/components/input-text/input-text.tsx @@ -33,6 +33,7 @@ import { CSS_UTILITY } from "../../utils/resources"; import { SetValueOrigin } from "../input/interfaces"; import { Alignment, Scale, Status } from "../interfaces"; import { getIconScale } from "../../utils/component"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { syncHiddenFormInput, TextualInputComponent } from "../input/common/input"; import { IconNameOrString } from "../icon/interfaces"; @@ -50,7 +51,10 @@ declare global { } } -/** @slot action - A slot for positioning a button next to the component. */ +/** + * @slot action - A slot for positioning a button next to the component. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class InputText extends LitElement implements LabelableComponent, FormComponent, InteractiveComponent, TextualInputComponent @@ -170,6 +174,8 @@ export class InputText /** Accessible name for the component's button or hyperlink. */ @property() label: string; + @property() labelText: string; + /** When present, the component is in the loading state and `calcite-progress` is displayed. */ @property({ reflect: true }) loading = false; @@ -615,6 +621,14 @@ export class InputText return ( + {this.labelText && ( + + )}
{ }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-input-time-picker`); + }); + describe("disabled", () => { disabled("calcite-input-time-picker"); }); diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.scss b/packages/calcite-components/src/components/input-time-picker/input-time-picker.scss index 31fccd573f2..4051a51bc3f 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.scss +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.scss @@ -127,5 +127,6 @@ calcite-time-picker { --calcite-time-picker-corner-radius: var(--calcite-corner-radius-round); } +@include form-internal-label(); @include form-validation-message(); @include base-component(); diff --git a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx index 13e7ba48481..a6d76ee93a8 100644 --- a/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx +++ b/packages/calcite-components/src/components/input-time-picker/input-time-picker.tsx @@ -29,6 +29,7 @@ import { HourFormat, TimePart } from "../../utils/time"; import { Scale, Status } from "../interfaces"; import { decimalPlaces } from "../../utils/math"; import { getIconScale } from "../../utils/component"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { getElementDir } from "../../utils/dom"; import { IconNameOrString } from "../icon/interfaces"; @@ -50,6 +51,9 @@ declare global { } } +/** + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class InputTimePicker extends LitElement implements FormComponent, InteractiveComponent, LabelableComponent, TimeComponent @@ -130,6 +134,9 @@ export class InputTimePicker /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** * When the component resides in a form, * specifies the maximum value. @@ -571,6 +578,14 @@ export class InputTimePicker const isInteractive = !this.disabled && !this.readOnly; return ( + {this.labelText && ( + + )}
html` > `; +export const internalLabel = (): string => html` + +`; + export const clearable = (): string => html` diff --git a/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx b/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx index 1986fc3a934..011c47be06a 100644 --- a/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx +++ b/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx @@ -29,8 +29,9 @@ import { IconNameOrString } from "../icon/interfaces"; import { useT9n } from "../../controllers/useT9n"; import type { Combobox } from "../combobox/combobox"; import type { Label } from "../label/label"; +import { SLOTS as COMBOBOX_SLOTS } from "../combobox/resources"; import { useSetFocus } from "../../controllers/useSetFocus"; -import { CSS } from "./resources"; +import { CSS, SLOTS } from "./resources"; import { createTimeZoneItems, findTimeZoneItemByProp, @@ -49,6 +50,9 @@ declare global { } } +/** + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class InputTimeZone extends LitElement implements FormComponent, InteractiveComponent, LabelableComponent @@ -109,6 +113,9 @@ export class InputTimeZone */ @property({ reflect: true }) form: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** Specifies the component's maximum number of options to display before displaying a scrollbar. */ @property({ reflect: true }) maxItems = 0; @@ -503,6 +510,7 @@ export class InputTimeZone clearDisabled={!this.clearable} disabled={this.disabled} label={this.messages.chooseTimeZone} + labelText={this.labelText} lang={this.messages._lang} maxItems={this.maxItems} oncalciteComboboxBeforeClose={this.onComboboxBeforeClose} @@ -521,6 +529,7 @@ export class InputTimeZone placeholderIcon="search" readOnly={this.readOnly} ref={this.setComboboxRef} + required={this.required} scale={this.scale} selectionMode={this.clearable ? "single" : "single-persist"} status={this.status} @@ -528,6 +537,7 @@ export class InputTimeZone validationMessage={this.validationMessage} > {this.renderItems()} + diff --git a/packages/calcite-components/src/components/input-time-zone/resources.ts b/packages/calcite-components/src/components/input-time-zone/resources.ts index f20cb242136..8749c69f17c 100644 --- a/packages/calcite-components/src/components/input-time-zone/resources.ts +++ b/packages/calcite-components/src/components/input-time-zone/resources.ts @@ -1,3 +1,7 @@ export const CSS = { offset: "offset", }; + +export const SLOTS = { + labelContent: "label-content", +}; diff --git a/packages/calcite-components/src/components/input/input.e2e.ts b/packages/calcite-components/src/components/input/input.e2e.ts index 054910c5cbf..28ab05df7db 100644 --- a/packages/calcite-components/src/components/input/input.e2e.ts +++ b/packages/calcite-components/src/components/input/input.e2e.ts @@ -8,6 +8,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, reflects, renders, @@ -256,6 +257,10 @@ describe("calcite-input", () => { }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-input`); + }); + describe("input type number increment/decrement functionality", () => { let page: E2EPage; beforeEach(async () => { diff --git a/packages/calcite-components/src/components/input/input.scss b/packages/calcite-components/src/components/input/input.scss index 60a1ebd461c..228e03be882 100755 --- a/packages/calcite-components/src/components/input/input.scss +++ b/packages/calcite-components/src/components/input/input.scss @@ -621,6 +621,7 @@ input { } } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/input/input.tsx b/packages/calcite-components/src/components/input/input.tsx index 4e97af2507b..3c77366649a 100644 --- a/packages/calcite-components/src/components/input/input.tsx +++ b/packages/calcite-components/src/components/input/input.tsx @@ -42,6 +42,7 @@ import { } from "../../utils/number"; import { CSS_UTILITY } from "../../utils/resources"; import { getIconScale } from "../../utils/component"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { IconNameOrString } from "../icon/interfaces"; import { useT9n } from "../../controllers/useT9n"; @@ -60,7 +61,10 @@ declare global { } } -/** @slot action - A slot for positioning a `calcite-button` next to the component. */ +/** + * @slot action - A slot for positioning a `calcite-button` next to the component. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class Input extends LitElement implements @@ -214,6 +218,9 @@ export class Input /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** When present, a busy indicator is displayed. */ @property({ reflect: true }) loading = false; @@ -1087,6 +1094,7 @@ export class Input placeholder={this.placeholder || ""} readOnly={this.readOnly} ref={this.setChildNumberElRef} + required={this.required} type="text" value={this.displayedValue} /> @@ -1140,6 +1148,15 @@ export class Input return ( + {this.labelText && ( + + )} +
{ ); }); + describe("InternalLabel", () => { + internalLabel(`calcite-radio-button-group`); + }); + describe("honors hidden attribute", () => { hidden("calcite-radio-button"); @@ -557,6 +571,10 @@ describe("calcite-radio-button-group", () => { expect(await getFocusedElementProp(page, "id")).toBe("shrubs"); }); + describe("translation support", () => { + t9n("calcite-radio-button-group"); + }); + describe("theme", () => { describe("default", () => { themed(html``, { diff --git a/packages/calcite-components/src/components/radio-button-group/radio-button-group.scss b/packages/calcite-components/src/components/radio-button-group/radio-button-group.scss index ef5ab0f20b1..5e9e003375e 100644 --- a/packages/calcite-components/src/components/radio-button-group/radio-button-group.scss +++ b/packages/calcite-components/src/components/radio-button-group/radio-button-group.scss @@ -58,5 +58,6 @@ ); } +@include form-internal-label(); @include form-validation-message(); @include base-component(); diff --git a/packages/calcite-components/src/components/radio-button-group/radio-button-group.tsx b/packages/calcite-components/src/components/radio-button-group/radio-button-group.tsx index 0d3e2a0d29c..2a8d228514b 100644 --- a/packages/calcite-components/src/components/radio-button-group/radio-button-group.tsx +++ b/packages/calcite-components/src/components/radio-button-group/radio-button-group.tsx @@ -13,11 +13,14 @@ import { } from "@arcgis/lumina"; import { createObserver } from "../../utils/observers"; import { Layout, Scale, Status } from "../interfaces"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; +import { useT9n } from "../../controllers/useT9n"; import { IconNameOrString } from "../icon/interfaces"; import type { RadioButton } from "../radio-button/radio-button"; import { useSetFocus } from "../../controllers/useSetFocus"; import { CSS, IDS } from "./resources"; +import T9nStrings from "./assets/t9n/messages.en.json"; import { styles } from "./radio-button-group.scss"; declare global { @@ -26,7 +29,10 @@ declare global { } } -/** @slot - A slot for adding `calcite-radio-button`s. */ +/** + * @slot - A slot for adding `calcite-radio-button`s. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class RadioButtonGroup extends LitElement { // #region Static Members @@ -36,6 +42,13 @@ export class RadioButtonGroup extends LitElement { // #region Private Properties + /** + * Made into a prop for testing purposes only + * + * @private + */ + messages = useT9n(); + private mutationObserver = createObserver("mutation", () => this.passPropsToRadioButtons()); private focusSetter = useSetFocus()(this); @@ -53,10 +66,16 @@ export class RadioButtonGroup extends LitElement { /** When present, interaction is prevented and the component is displayed with lower opacity. */ @property({ reflect: true }) disabled = false; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** Defines the layout of the component. */ @property({ reflect: true }) layout: Extract<"horizontal" | "vertical" | "grid", Layout> = "horizontal"; + /** Use this property to override individual strings used by the component. */ + @property() messageOverrides?: typeof this.messages._overrides; + /** * Specifies the name of the component on form submission. Must be unique to other component instances. * @@ -196,9 +215,17 @@ export class RadioButtonGroup extends LitElement { this.el.role = "radiogroup"; return ( <> + {this.labelText && ( + + )}
diff --git a/packages/calcite-components/src/components/radio-button/radio-button.e2e.ts b/packages/calcite-components/src/components/radio-button/radio-button.e2e.ts index 06e965980b4..3a2af5fb633 100644 --- a/packages/calcite-components/src/components/radio-button/radio-button.e2e.ts +++ b/packages/calcite-components/src/components/radio-button/radio-button.e2e.ts @@ -8,6 +8,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, reflects, renders, @@ -230,6 +231,10 @@ describe("calcite-radio-button", () => { }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-radio-button`); + }); + describe("reflects", () => { reflects("calcite-radio-button", [ { propertyName: "checked", value: true }, diff --git a/packages/calcite-components/src/components/radio-button/radio-button.scss b/packages/calcite-components/src/components/radio-button/radio-button.scss index 71e2891171b..54a69a16fe1 100644 --- a/packages/calcite-components/src/components/radio-button/radio-button.scss +++ b/packages/calcite-components/src/components/radio-button/radio-button.scss @@ -18,6 +18,7 @@ .container { @apply relative outline-none; + display: flex; } .radio { @@ -142,5 +143,6 @@ } } +@include form-internal-label(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/radio-button/radio-button.tsx b/packages/calcite-components/src/components/radio-button/radio-button.tsx index 5faf35ed94f..ca0ec63f2f1 100644 --- a/packages/calcite-components/src/components/radio-button/radio-button.tsx +++ b/packages/calcite-components/src/components/radio-button/radio-button.tsx @@ -15,6 +15,7 @@ import { updateHostInteraction, } from "../../utils/interactive"; import { connectLabel, disconnectLabel, getLabelText, LabelableComponent } from "../../utils/label"; +import { InternalLabel } from "../functional/InternalLabel"; import { Scale } from "../interfaces"; import type { Label } from "../label/label"; import { useSetFocus } from "../../controllers/useSetFocus"; @@ -91,6 +92,9 @@ export class RadioButton */ @property() label?: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** * Specifies the name of the component. Can be inherited from `calcite-radio-button-group`. * @@ -493,6 +497,7 @@ export class RadioButton tabIndex={tabIndex} >
+ {this.labelText && }
diff --git a/packages/calcite-components/src/components/rating/rating.e2e.ts b/packages/calcite-components/src/components/rating/rating.e2e.ts index 8a350ef333b..36097dd55cd 100644 --- a/packages/calcite-components/src/components/rating/rating.e2e.ts +++ b/packages/calcite-components/src/components/rating/rating.e2e.ts @@ -7,6 +7,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, reflects, renders, @@ -49,6 +50,10 @@ describe("calcite-rating", () => { }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-rating`); + }); + describe("focuses the first star when the label is clicked and no-rating value exists", () => { labelable("calcite-rating", { shadowFocusTargetSelector: "label[data-value='1']", diff --git a/packages/calcite-components/src/components/rating/rating.scss b/packages/calcite-components/src/components/rating/rating.scss index a0242644bff..e5ba2c99704 100644 --- a/packages/calcite-components/src/components/rating/rating.scss +++ b/packages/calcite-components/src/components/rating/rating.scss @@ -112,6 +112,7 @@ calcite-chip { @apply sr-only; } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/rating/rating.tsx b/packages/calcite-components/src/components/rating/rating.tsx index fa3d227ec2d..d5de573da6b 100644 --- a/packages/calcite-components/src/components/rating/rating.tsx +++ b/packages/calcite-components/src/components/rating/rating.tsx @@ -22,8 +22,9 @@ import { InteractiveContainer, updateHostInteraction, } from "../../utils/interactive"; -import { connectLabel, disconnectLabel, LabelableComponent } from "../../utils/label"; +import { connectLabel, disconnectLabel, LabelableComponent, getLabelText } from "../../utils/label"; import { Scale, Status } from "../interfaces"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { IconNameOrString } from "../icon/interfaces"; import { useT9n } from "../../controllers/useT9n"; @@ -41,6 +42,9 @@ declare global { } } +/** + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class Rating extends LitElement implements LabelableComponent, FormComponent, InteractiveComponent @@ -108,6 +112,9 @@ export class Rating */ @property({ reflect: true }) form: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** Use this property to override individual strings used by the component. */ @property() messageOverrides?: typeof this.messages._overrides; @@ -396,6 +403,14 @@ export class Rating return ( + {this.labelText && ( + + )}
{this.messages.rating} {this.starsMap.map( @@ -421,6 +436,8 @@ export class Rating { }); }); + describe("InternalLabel", () => { + internalLabel(`calcite-segmented-control`); + }); + describe("is form-associated", () => { describe("unselected value", () => { formAssociated( @@ -501,6 +507,10 @@ describe("calcite-segmented-control", () => { }); }); + describe("translation support", () => { + t9n("calcite-segmented-control"); + }); + describe("theme", () => { themed("calcite-segmented-control", { "--calcite-segmented-control-border-color": { diff --git a/packages/calcite-components/src/components/segmented-control/segmented-control.scss b/packages/calcite-components/src/components/segmented-control/segmented-control.scss index 36ddc1502a8..3571f759f00 100644 --- a/packages/calcite-components/src/components/segmented-control/segmented-control.scss +++ b/packages/calcite-components/src/components/segmented-control/segmented-control.scss @@ -43,6 +43,7 @@ } } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/segmented-control/segmented-control.tsx b/packages/calcite-components/src/components/segmented-control/segmented-control.tsx index 6f978bbf5a2..e4b89931719 100644 --- a/packages/calcite-components/src/components/segmented-control/segmented-control.tsx +++ b/packages/calcite-components/src/components/segmented-control/segmented-control.tsx @@ -24,14 +24,17 @@ import { InteractiveContainer, updateHostInteraction, } from "../../utils/interactive"; -import { connectLabel, disconnectLabel, LabelableComponent } from "../../utils/label"; +import { connectLabel, disconnectLabel, LabelableComponent, getLabelText } from "../../utils/label"; import { Appearance, Layout, Scale, Status, Width } from "../interfaces"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { IconNameOrString } from "../icon/interfaces"; import type { SegmentedControlItem } from "../segmented-control-item/segmented-control-item"; import type { Label } from "../label/label"; +import { useT9n } from "../../controllers/useT9n"; import { useSetFocus } from "../../controllers/useSetFocus"; import { CSS, IDS } from "./resources"; +import T9nStrings from "./assets/t9n/messages.en.json"; import { styles } from "./segmented-control.scss"; declare global { @@ -40,7 +43,10 @@ declare global { } } -/** @slot - A slot for adding `calcite-segmented-control-item`s. */ +/** + * @slot - A slot for adding `calcite-segmented-control-item`s. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class SegmentedControl extends LitElement implements LabelableComponent, FormComponent, InteractiveComponent @@ -61,6 +67,13 @@ export class SegmentedControl labelEl: Label["el"]; + /** + * Made into a prop for testing purposes only + * + * @private + */ + messages = useT9n(); + private focusSetter = useSetFocus()(this); // #endregion @@ -86,6 +99,12 @@ export class SegmentedControl /** Defines the layout of the component. */ @property({ reflect: true }) layout: Extract<"horizontal" | "vertical", Layout> = "horizontal"; + /** When provided, displays label text on the component. */ + @property() labelText: string; + + /** Use this property to override individual strings used by the component. */ + @property() messageOverrides?: typeof this.messages._overrides; + /** * Specifies the name of the component. * @@ -401,9 +420,19 @@ export class SegmentedControl this.el.role = "radiogroup"; return ( <> + {this.labelText && ( + + )}
diff --git a/packages/calcite-components/src/components/select/select.e2e.ts b/packages/calcite-components/src/components/select/select.e2e.ts index f276ba659c3..78698d1eb68 100644 --- a/packages/calcite-components/src/components/select/select.e2e.ts +++ b/packages/calcite-components/src/components/select/select.e2e.ts @@ -8,9 +8,11 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, reflects, renders, + t9n, themed, } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; @@ -43,6 +45,10 @@ describe("calcite-select", () => { focusable(simpleTestMarkup); }); + describe("InternalLabel", () => { + internalLabel(`calcite-select`); + }); + describe("defaults", () => { defaults("calcite-select", [ { propertyName: "scale", defaultValue: "m" }, @@ -461,6 +467,10 @@ describe("calcite-select", () => { ); }); + describe("translation support", () => { + t9n("calcite-select"); + }); + describe("theme", () => { themed( html` diff --git a/packages/calcite-components/src/components/select/select.scss b/packages/calcite-components/src/components/select/select.scss index aeafa4414e7..2cc15c1f168 100644 --- a/packages/calcite-components/src/components/select/select.scss +++ b/packages/calcite-components/src/components/select/select.scss @@ -123,6 +123,7 @@ select:disabled { @apply border-color-transparent; } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/select/select.tsx b/packages/calcite-components/src/components/select/select.tsx index 2c8409ccb34..b4c73b3271c 100644 --- a/packages/calcite-components/src/components/select/select.tsx +++ b/packages/calcite-components/src/components/select/select.tsx @@ -9,6 +9,7 @@ import { JsxNode, stringOrBoolean, } from "@arcgis/lumina"; +import { useT9n } from "../../controllers/useT9n"; import { afterConnectDefaultValueSet, connectForm, @@ -26,6 +27,7 @@ import { connectLabel, disconnectLabel, getLabelText, LabelableComponent } from import { createObserver } from "../../utils/observers"; import { Scale, Status, Width } from "../interfaces"; import { getIconScale } from "../../utils/component"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { IconNameOrString } from "../icon/interfaces"; import type { Option } from "../option/option"; @@ -33,6 +35,7 @@ import type { OptionGroup } from "../option-group/option-group"; import type { Label } from "../label/label"; import { useSetFocus } from "../../controllers/useSetFocus"; import { styles } from "./select.scss"; +import T9nStrings from "./assets/t9n/messages.en.json"; import { CSS, IDS } from "./resources"; declare global { @@ -52,7 +55,10 @@ function isOptionGroup(optionOrGroup: OptionOrGroup): optionOrGroup is OptionGro return optionOrGroup.tagName === "CALCITE-OPTION-GROUP"; } -/** @slot - A slot for adding `calcite-option`s. */ +/** + * @slot - A slot for adding `calcite-option`s. + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class Select extends LitElement implements LabelableComponent, FormComponent, InteractiveComponent @@ -77,6 +83,13 @@ export class Select private selectEl: HTMLSelectElement; + /** + * Made into a prop for testing purposes only + * + * @private + */ + messages = useT9n(); + private focusSetter = useSetFocus()(this); // #endregion @@ -100,6 +113,9 @@ export class Select */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** * Specifies the name of the component. * @@ -160,6 +176,9 @@ export class Select /** Specifies the width of the component. [Deprecated] The `"half"` value is deprecated, use `"full"` instead. */ @property({ reflect: true }) width: Extract = "auto"; + /** Use this property to override individual strings used by the component. */ + @property() messageOverrides?: typeof this.messages._overrides; + // #endregion // #region Public Methods @@ -404,6 +423,14 @@ export class Select return ( + {this.labelText && ( + + )}
diff --git a/packages/calcite-components/src/components/slider/slider.e2e.ts b/packages/calcite-components/src/components/slider/slider.e2e.ts index 25f526372a1..5ac2692dda3 100644 --- a/packages/calcite-components/src/components/slider/slider.e2e.ts +++ b/packages/calcite-components/src/components/slider/slider.e2e.ts @@ -7,9 +7,11 @@ import { disabled, formAssociated, hidden, + internalLabel, labelable, reflects, renders, + t9n, themed, } from "../../tests/commonTests"; import { findAll, getElementRect, getElementXY, isElementFocused } from "../../tests/utils/puppeteer"; @@ -108,6 +110,10 @@ describe("calcite-slider", () => { disabled("calcite-slider"); }); + describe("InternalLabel", () => { + internalLabel(`calcite-slider`); + }); + it("sets aria attributes properly for single value", async () => { const page = await newE2EPage(); await page.setContent(` @@ -1223,6 +1229,10 @@ describe("calcite-slider", () => { }); }); + describe("translation support", () => { + t9n("calcite-slider"); + }); + describe("themed", () => { describe("default", () => { themed(html``, { diff --git a/packages/calcite-components/src/components/slider/slider.scss b/packages/calcite-components/src/components/slider/slider.scss index 8b744c8c887..3dd88510555 100644 --- a/packages/calcite-components/src/components/slider/slider.scss +++ b/packages/calcite-components/src/components/slider/slider.scss @@ -400,6 +400,7 @@ } } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/slider/slider.tsx b/packages/calcite-components/src/components/slider/slider.tsx index bd34927e739..0d45873b87f 100644 --- a/packages/calcite-components/src/components/slider/slider.tsx +++ b/packages/calcite-components/src/components/slider/slider.tsx @@ -13,6 +13,7 @@ import { } from "@arcgis/lumina"; import { guid } from "../../utils/guid"; import { intersects, isPrimaryPointerButton } from "../../utils/dom"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { afterConnectDefaultValueSet, @@ -41,6 +42,7 @@ import { useSetFocus } from "../../controllers/useSetFocus"; import { CSS, IDS, maxTickElementThreshold } from "./resources"; import { ActiveSliderProperty, SetValueProperty, SideOffset, ThumbType } from "./interfaces"; import { styles } from "./slider.scss"; +import T9nStrings from "./assets/t9n/messages.en.json"; declare global { interface DeclareElements { @@ -52,6 +54,9 @@ function isRange(value: number | number[]): value is number[] { return Array.isArray(value); } +/** + * @slot label-content - A slot for rendering content next to the component's `labelText`. + */ export class Slider extends LitElement implements LabelableComponent, FormComponent, InteractiveComponent @@ -162,7 +167,12 @@ export class Slider private maxHandle: HTMLDivElement; - messages = useT9n>({ name: null }); + /** + * Made into a prop for testing purposes only + * + * @private + */ + messages = useT9n(); private minHandle: HTMLDivElement; @@ -259,6 +269,12 @@ export class Slider /** Accessible name for first (or only) handle, such as `"Temperature, lower bound"`. */ @property() minLabel: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + + /** Use this property to override individual strings used by the component. */ + @property() messageOverrides?: typeof this.messages._overrides; + /** For multiple selections, the component's lower value. */ @property() minValue: number; @@ -1144,10 +1160,19 @@ export class Slider return ( + {this.labelText && ( + + )}
{ focusable("calcite-switch"); }); + describe("InternalLabel", () => { + internalLabel(`calcite-switch`); + }); + it("toggles the checked attributes appropriately when clicked", async () => { const page = await newE2EPage(); await page.setContent(""); diff --git a/packages/calcite-components/src/components/switch/switch.scss b/packages/calcite-components/src/components/switch/switch.scss index bd80c3c03c1..1f2cb2c904a 100644 --- a/packages/calcite-components/src/components/switch/switch.scss +++ b/packages/calcite-components/src/components/switch/switch.scss @@ -63,6 +63,7 @@ .container { @apply outline-0; + display: flex; } .track { @@ -123,5 +124,6 @@ } } +@include form-internal-label(); @include hidden-form-input(); @include base-component(); diff --git a/packages/calcite-components/src/components/switch/switch.tsx b/packages/calcite-components/src/components/switch/switch.tsx index 9ae02844e86..02b9ac2fcc4 100644 --- a/packages/calcite-components/src/components/switch/switch.tsx +++ b/packages/calcite-components/src/components/switch/switch.tsx @@ -15,6 +15,7 @@ import { isActivationKey } from "../../utils/key"; import { connectLabel, disconnectLabel, getLabelText, LabelableComponent } from "../../utils/label"; import { Scale } from "../interfaces"; import type { Label } from "../label/label"; +import { InternalLabel } from "../functional/InternalLabel"; import { useSetFocus } from "../../controllers/useSetFocus"; import { CSS } from "./resources"; import { styles } from "./switch.scss"; @@ -69,6 +70,12 @@ export class Switch /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text at the end of the component */ + @property() labelTextEnd: string; + + /** When provided, displays label text at the start of the component */ + @property() labelTextStart: string; + /** * Specifies the name of the component. * @@ -185,9 +192,25 @@ export class Switch role="switch" tabIndex={0} > + {this.labelTextStart && ( + + )}
+ {this.labelTextEnd && ( + + )}
diff --git a/packages/calcite-components/src/components/text-area/text-area.e2e.ts b/packages/calcite-components/src/components/text-area/text-area.e2e.ts index 4c1a498cd35..859c1f9dc2e 100644 --- a/packages/calcite-components/src/components/text-area/text-area.e2e.ts +++ b/packages/calcite-components/src/components/text-area/text-area.e2e.ts @@ -8,6 +8,7 @@ import { focusable, formAssociated, hidden, + internalLabel, labelable, reflects, renders, @@ -106,6 +107,10 @@ describe("calcite-text-area", () => { focusable("calcite-text-area"); }); + describe("InternalLabel", () => { + internalLabel(`calcite-text-area`); + }); + describe("is form associated", () => { formAssociated("calcite-text-area", { testValue: "zion national park", diff --git a/packages/calcite-components/src/components/text-area/text-area.scss b/packages/calcite-components/src/components/text-area/text-area.scss index 9c76d25322d..97381f2cde9 100644 --- a/packages/calcite-components/src/components/text-area/text-area.scss +++ b/packages/calcite-components/src/components/text-area/text-area.scss @@ -232,6 +232,7 @@ } } +@include form-internal-label(); @include form-validation-message(); @include hidden-form-input(); @include disabled(); diff --git a/packages/calcite-components/src/components/text-area/text-area.tsx b/packages/calcite-components/src/components/text-area/text-area.tsx index 5fd3d816a82..ffdb540ec75 100644 --- a/packages/calcite-components/src/components/text-area/text-area.tsx +++ b/packages/calcite-components/src/components/text-area/text-area.tsx @@ -30,6 +30,7 @@ import { } from "../../utils/interactive"; import { guid } from "../../utils/guid"; import { Status } from "../interfaces"; +import { InternalLabel } from "../functional/InternalLabel"; import { Validation } from "../functional/Validation"; import { syncHiddenFormInput, TextualInputComponent } from "../input/common/input"; import { IconNameOrString } from "../icon/interfaces"; @@ -50,6 +51,7 @@ declare global { /** * @slot - A slot for adding text. + * @slot label-content - A slot for rendering content next to the component's `labelText`. * @slot footer-start - A slot for adding content to the start of the component's footer. * @slot footer-end - A slot for adding content to the end of the component's footer. */ @@ -181,6 +183,9 @@ export class TextArea /** Accessible name for the component. */ @property() label: string; + /** When provided, displays label text on the component. */ + @property() labelText: string; + /** * When present, prevents input beyond the `maxLength` value, mimicking native text area behavior. */ @@ -481,6 +486,14 @@ export class TextArea return (
+ {this.labelText && ( + + )}