diff --git a/packages/calcite-components/src/components/checkbox/checkbox.scss b/packages/calcite-components/src/components/checkbox/checkbox.scss index 971b7d822ff..0d7d88f1f93 100644 --- a/packages/calcite-components/src/components/checkbox/checkbox.scss +++ b/packages/calcite-components/src/components/checkbox/checkbox.scss @@ -43,6 +43,16 @@ color: theme("backgroundColor.background"); } } + +:host([status="invalid"]:not([checked])) { + .check-svg { + box-shadow: inset 0 0 0 1px var(--calcite-ui-danger); + } + .toggle:focus { + @apply focus-outset-danger; + } +} + :host([checked]), :host([indeterminate]) { .check-svg { diff --git a/packages/calcite-components/src/components/checkbox/checkbox.stories.ts b/packages/calcite-components/src/components/checkbox/checkbox.stories.ts index c3fc522678d..2d171026c67 100644 --- a/packages/calcite-components/src/components/checkbox/checkbox.stories.ts +++ b/packages/calcite-components/src/components/checkbox/checkbox.stories.ts @@ -19,6 +19,7 @@ export const simple = (): string => html` ${boolean("disabled", false)} ${boolean("indeterminate", false)} scale="${select("scale", ["s", "m", "l"], "m")}" + status="${select("status", ["idle", "invalid", "valid"], "idle")}" > ${text("label", "Checkbox")} diff --git a/packages/calcite-components/src/components/checkbox/checkbox.tsx b/packages/calcite-components/src/components/checkbox/checkbox.tsx index 9f9f6cb89e4..7199fff7cdf 100644 --- a/packages/calcite-components/src/components/checkbox/checkbox.tsx +++ b/packages/calcite-components/src/components/checkbox/checkbox.tsx @@ -31,7 +31,7 @@ import { setComponentLoaded, setUpLoadableComponent, } from "../../utils/loadable"; -import { Scale } from "../interfaces"; +import { Scale, Status } from "../interfaces"; @Component({ tag: "calcite-checkbox", @@ -96,6 +96,9 @@ export class Checkbox /** Specifies the size of the component. */ @Prop({ reflect: true }) scale: Scale = "m"; + /** Specifies the status of the input field, which determines message and icons. */ + @Prop({ reflect: true }) status: Status = "idle"; + /** The component's value. */ @Prop() value: any; diff --git a/packages/calcite-components/src/components/combobox/combobox.scss b/packages/calcite-components/src/components/combobox/combobox.scss index 72c01847b71..1178f6c8ef1 100644 --- a/packages/calcite-components/src/components/combobox/combobox.scss +++ b/packages/calcite-components/src/components/combobox/combobox.scss @@ -66,6 +66,14 @@ @apply focus-inset; } +:host([status="invalid"]) .wrapper { + @apply border-color-danger; +} + +:host([status="invalid"]:focus-within) .wrapper { + @apply focus-inset-danger; +} + .wrapper--single { padding-block: 0; padding-inline: var(--calcite-combobox-item-spacing-unit-l); diff --git a/packages/calcite-components/src/components/combobox/combobox.stories.ts b/packages/calcite-components/src/components/combobox/combobox.stories.ts index 36ff8ec8f67..0619cb18bf8 100644 --- a/packages/calcite-components/src/components/combobox/combobox.stories.ts +++ b/packages/calcite-components/src/components/combobox/combobox.stories.ts @@ -23,6 +23,7 @@ export const single = (): string => html` max-items="${number("max-items", 0)}" placeholder="${text("placeholder", "placeholder")}" scale="${select("scale", ["s", "m", "l"], "m")}" + status="${select("status", ["idle", "invalid", "valid"], "idle")}" > @@ -296,6 +297,7 @@ export const nestedItems = (): string => html` ${boolean("disabled", false)} ${boolean("allow-custom-values", false)} max-items="${number("max-items", 0)}" + status="${select("status", ["idle", "invalid", "valid"], "idle")}" > diff --git a/packages/calcite-components/src/components/combobox/combobox.tsx b/packages/calcite-components/src/components/combobox/combobox.tsx index 5ab414a4814..cabc7664d76 100644 --- a/packages/calcite-components/src/components/combobox/combobox.tsx +++ b/packages/calcite-components/src/components/combobox/combobox.tsx @@ -66,7 +66,7 @@ import { T9nComponent, updateMessages, } from "../../utils/t9n"; -import { Scale, SelectionMode } from "../interfaces"; +import { Scale, SelectionMode, Status } from "../interfaces"; import { ComboboxMessages } from "./assets/combobox/t9n"; import { ComboboxChildElement, SelectionDisplay } from "./interfaces"; import { ComboboxChildSelector, ComboboxItem, ComboboxItemGroup, CSS } from "./resources"; @@ -224,6 +224,9 @@ export class Combobox /** Specifies the size of the component. */ @Prop({ reflect: true }) scale: Scale = "m"; + /** Specifies the status of the input field, which determines message and icons. */ + @Prop({ reflect: true }) status: Status = "idle"; + @Watch("selectionMode") @Watch("scale") handlePropsChange(): void { diff --git a/packages/calcite-components/src/components/input-text/input-text.stories.ts b/packages/calcite-components/src/components/input-text/input-text.stories.ts index db813e2eed8..6ee29465529 100644 --- a/packages/calcite-components/src/components/input-text/input-text.stories.ts +++ b/packages/calcite-components/src/components/input-text/input-text.stories.ts @@ -17,7 +17,6 @@ export const simple = (): string => html` html` ${boolean("disabled", false)} mode="${select("mode", ["offset", "name"], "offset")}" scale="${select("scale", ["s", "m", "l"], "m")}" + status="${select("status", ["idle", "invalid", "valid"], "idle")}" > `; 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 d8fe3b51d29..789fa5bd6e7 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 @@ -20,7 +20,7 @@ import { SupportedLocale, } from "../../utils/locale"; import { TimeZoneItem, TimeZoneMode } from "./interfaces"; -import { Scale } from "../interfaces"; +import { Scale, Status } from "../interfaces"; import { connectMessages, disconnectMessages, @@ -157,6 +157,9 @@ export class InputTimeZone /** Specifies the size of the component. */ @Prop({ reflect: true }) scale: Scale = "m"; + /** Specifies the status of the input field, which determines message and icons. */ + @Prop({ reflect: true }) status: Status = "idle"; + /** * The component's value, where the value is the time zone offset or the difference, in minutes, between the selected time zone and UTC. * @@ -381,6 +384,7 @@ export class InputTimeZone overlayPositioning={this.overlayPositioning} scale={this.scale} selectionMode="single-persist" + status={this.status} // eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530) ref={this.setComboboxRef} > diff --git a/packages/calcite-components/src/components/select/select.scss b/packages/calcite-components/src/components/select/select.scss index 2eff717603c..6fc71700a79 100644 --- a/packages/calcite-components/src/components/select/select.scss +++ b/packages/calcite-components/src/components/select/select.scss @@ -105,6 +105,17 @@ select:disabled { border-inline-width: theme("borderWidth.0") theme("borderWidth.DEFAULT"); } +:host([status="invalid"]) { + select, + .icon-container { + @apply border-color-danger; + } + select:focus, + .icon-container:focus { + @apply focus-inset-danger; + } +} + .select:focus ~ .icon-container { @apply border-color-transparent; } diff --git a/packages/calcite-components/src/components/select/select.stories.ts b/packages/calcite-components/src/components/select/select.stories.ts index 3960797fba1..38faf1f7878 100644 --- a/packages/calcite-components/src/components/select/select.stories.ts +++ b/packages/calcite-components/src/components/select/select.stories.ts @@ -6,7 +6,7 @@ import { modesDarkDefault, } from "../../../.storybook/utils"; import { html } from "../../../support/formatting"; -import { boolean, text } from "@storybook/addon-knobs"; +import { select, boolean, text } from "@storybook/addon-knobs"; import selectReadme from "../select/readme.md"; import optionReadme from "../option/readme.md"; import optionGroupReadme from "../option-group/readme.md"; @@ -27,6 +27,30 @@ const createSelectAttributes: (options?: { exceptions: string[] }) => Attributes return this; }, }, + { + name: "status", + commit(): Attribute { + this.value = select("status", ["idle", "invalid", "valid"], "idle", group); + delete this.build; + return this; + }, + }, + { + name: "width", + commit(): Attribute { + this.value = select("width", ["auto", "full", "half"], "auto", group); + delete this.build; + return this; + }, + }, + { + name: "scale", + commit(): Attribute { + this.value = select("scale", ["s", "m", "l"], "m", group); + delete this.build; + return this; + }, + }, ], exceptions ); diff --git a/packages/calcite-components/src/components/select/select.tsx b/packages/calcite-components/src/components/select/select.tsx index 0b72064f814..494f900798f 100644 --- a/packages/calcite-components/src/components/select/select.tsx +++ b/packages/calcite-components/src/components/select/select.tsx @@ -33,7 +33,7 @@ import { setUpLoadableComponent, } from "../../utils/loadable"; import { createObserver } from "../../utils/observers"; -import { Scale, Width } from "../interfaces"; +import { Scale, Status, Width } from "../interfaces"; import { CSS } from "./resources"; import { getIconScale } from "../../utils/component"; @@ -77,8 +77,7 @@ export class Select * * When not set, the component will be associated with its ancestor form element, if any. */ - @Prop({ reflect: true }) - form: string; + @Prop({ reflect: true }) form: string; /** * Accessible name for the component. @@ -105,6 +104,9 @@ export class Select */ @Prop({ reflect: true }) scale: Scale = "m"; + /** Specifies the status of the input field, which determines message and icons. */ + @Prop({ reflect: true }) status: Status = "idle"; + /** The component's `selectedOption` value. */ @Prop({ mutable: true }) value: string = null; 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 281c15cfe57..d0882418ba9 100644 --- a/packages/calcite-components/src/components/text-area/text-area.scss +++ b/packages/calcite-components/src/components/text-area/text-area.scss @@ -140,6 +140,15 @@ textarea { } } +:host([status="invalid"]) { + textarea { + @apply border-color-danger; + } + textarea:focus { + @apply focus-inset-danger; + } +} + .readonly { @apply bg-background font-medium; } diff --git a/packages/calcite-components/src/components/text-area/text-area.stories.ts b/packages/calcite-components/src/components/text-area/text-area.stories.ts index 8a7b0bb52df..cb0cdda014a 100644 --- a/packages/calcite-components/src/components/text-area/text-area.stories.ts +++ b/packages/calcite-components/src/components/text-area/text-area.stories.ts @@ -14,6 +14,7 @@ export default { export const simple = (): string => html` Checkbox + +
+ +
unchecked invalid
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+