Skip to content

Commit

Permalink
feat(radio-button-group, segmented control): add validationMessage, v…
Browse files Browse the repository at this point in the history
…alidationIcon, and status properties (#8561)

**Related Issue:** #8057

## Summary

Add `validationMessage`, `validationIcon`, and `status` properties to
`calcite-radio-button-group` and `calcite-segmented-control` for form
validation.
  • Loading branch information
benelan authored Jan 16, 2024
1 parent d198a29 commit d4c5efc
Show file tree
Hide file tree
Showing 13 changed files with 621 additions and 40 deletions.
48 changes: 48 additions & 0 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3796,6 +3796,18 @@ export namespace Components {
* Sets focus on the fist focusable `calcite-radio-button` element in the component.
*/
"setFocus": () => Promise<void>;
/**
* Specifies the status of the validation message.
*/
"status": Status;
/**
* Specifies the validation icon to display under the component.
*/
"validationIcon": string | boolean;
/**
* Specifies the validation message to display under the component.
*/
"validationMessage": string;
}
interface CalciteRating {
/**
Expand Down Expand Up @@ -3903,6 +3915,18 @@ export namespace Components {
* Sets focus on the component.
*/
"setFocus": () => Promise<void>;
/**
* Specifies the status of the validation message.
*/
"status": Status;
/**
* Specifies the validation icon to display under the component.
*/
"validationIcon": string | boolean;
/**
* Specifies the validation message to display under the component.
*/
"validationMessage": string;
/**
* The component's `selectedItem` value.
*/
Expand Down Expand Up @@ -11250,6 +11274,18 @@ declare namespace LocalJSX {
* @readonly
*/
"selectedItem"?: HTMLCalciteRadioButtonElement;
/**
* Specifies the status of the validation message.
*/
"status"?: Status;
/**
* Specifies the validation icon to display under the component.
*/
"validationIcon"?: string | boolean;
/**
* Specifies the validation message to display under the component.
*/
"validationMessage"?: string;
}
interface CalciteRating {
/**
Expand Down Expand Up @@ -11357,6 +11393,18 @@ declare namespace LocalJSX {
* @readonly
*/
"selectedItem"?: HTMLCalciteSegmentedControlItemElement;
/**
* Specifies the status of the validation message.
*/
"status"?: Status;
/**
* Specifies the validation icon to display under the component.
*/
"validationIcon"?: string | boolean;
/**
* Specifies the validation message to display under the component.
*/
"validationMessage"?: string;
/**
* The component's `selectedItem` value.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe("calcite-radio-button-group", () => {
defaults("calcite-radio-button-group", [
{ propertyName: "layout", defaultValue: "horizontal" },
{ propertyName: "scale", defaultValue: "m" },
{ propertyName: "status", defaultValue: "idle" },
{ propertyName: "validationIcon", defaultValue: undefined },
{ propertyName: "validationMessage", defaultValue: undefined },
]);
});

Expand Down Expand Up @@ -108,6 +111,8 @@ describe("calcite-radio-button-group", () => {
{ propertyName: "name", value: "reflects-name" },
{ propertyName: "required", value: true },
{ propertyName: "scale", value: "m" },
{ propertyName: "status", value: "invalid" },
{ propertyName: "validationIcon", value: true },
]);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
:host {
@apply flex flex-col;
}

:host > .item-wrapper {
@apply flex;
max-inline-size: 100vw;
}
:host([layout="horizontal"]) {

:host([layout="horizontal"]) > .item-wrapper {
@apply flex-row flex-wrap;
}
:host([layout="horizontal"][scale="s"]) {
@apply gap-4;

:host([layout="horizontal"][scale="s"]) > .item-wrapper {
@apply gap-x-4;
}
:host([layout="horizontal"][scale="m"]) {
@apply gap-5;

:host([layout="horizontal"][scale="m"]) > .item-wrapper {
@apply gap-x-5;
}
:host([layout="horizontal"][scale="l"]) {
@apply gap-6;

:host([layout="horizontal"][scale="l"]) > .item-wrapper {
@apply gap-x-6;
}
:host([layout="vertical"]) {

:host([layout="vertical"]) > .item-wrapper {
@apply flex-col;
}

:host([scale="s"]) calcite-input-message {
--calcite-input-message-spacing-value: calc(var(--calcite-spacing-xxs) * -1);
}

:host([scale="m"]) calcite-input-message {
--calcite-input-message-spacing-value: calc(var(--calcite-spacing-sm) * -1);
}

:host([scale="l"]) calcite-input-message {
--calcite-input-message-spacing-value: calc(var(--calcite-spacing-md) * -1);
}

@include form-validation-message();
@include base-component();
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { select } from "@storybook/addon-knobs";
import { boolean, storyFilters } from "../../../.storybook/helpers";
import { select, text } from "@storybook/addon-knobs";
import { boolean, iconNames, storyFilters } from "../../../.storybook/helpers";
import { modesDarkDefault } from "../../../.storybook/utils";
import readme from "./readme.md";
import { html } from "../../../support/formatting";
import readme from "./readme.md";

export default {
title: "Components/Controls/Radio/Radio Button Group",
Expand Down Expand Up @@ -39,15 +39,20 @@ export const simple = (): string => html`
</calcite-radio-button-group>
`;

export const darkModeRTL_TestOnly = (): string => html`
// We created a separate story for validation-message because it is not possible
// to set a text knob's value to undefined. Unfortunately, this makes the CSS
// attribute selector truthy, which sets "--calcite-label-margin-bottom: 0;",
// causing a Chromatic diff. See calcite-radio-button-group.scss.
export const validationMessage_NoTest = (): string => html`
<calcite-radio-button-group
class="calcite-mode-dark"
dir="rtl"
name="dark"
name="simple"
${boolean("disabled", false)}
${boolean("hidden", false)}
layout="${select("layout", ["horizontal", "vertical"], "horizontal")}"
scale="${select("scale", ["s", "m", "l"], "m")}"
status="${select("status", ["idle", "invalid", "valid"], "invalid")}"
validation-icon="${select("validation-icon", ["", ...iconNames], "")}"
validation-message="${text("validation-message", "Please fill out this field.")}"
>
<calcite-label layout="inline">
<calcite-radio-button value="react"></calcite-radio-button>
Expand All @@ -68,4 +73,115 @@ export const darkModeRTL_TestOnly = (): string => html`
</calcite-radio-button-group>
`;

export const darkModeRTL_TestOnly = (): string => html`
<calcite-radio-button-group
class="calcite-mode-dark"
dir="rtl"
name="dark"
layout="vertical"
status="valid"
validation-icon
validation-message="Thanks for not selecting Ember"
>
<calcite-label layout="inline">
<calcite-radio-button value="react" checked></calcite-radio-button>
React
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="ember"></calcite-radio-button>
Ember
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="angular"></calcite-radio-button>
Angular
</calcite-label>
<calcite-label layout="inline">
<calcite-radio-button value="vue"></calcite-radio-button>
Vue
</calcite-label>
</calcite-radio-button-group>
`;

darkModeRTL_TestOnly.parameters = { modes: modesDarkDefault };

export const validationMessage_TestOnly = (): string => html`
<style>
.container {
display: flex;
flex-direction: column;
width: 400px;
height: 200px;
gap: 20px;
}
</style>
<div class="container">
<calcite-radio-button-group
layout="horizontal"
name="validation"
required
scale="s"
status="invalid"
validation-icon
validation-message="Please select an option."
>
<calcite-label layout="inline" scale="s">
<calcite-radio-button value="one" scale="s"></calcite-radio-button>
One
</calcite-label>
<calcite-label layout="inline" scale="s">
<calcite-radio-button value="two" scale="s"></calcite-radio-button>
Two
</calcite-label>
<calcite-label layout="inline" scale="s">
<calcite-radio-button value="three" scale="s"></calcite-radio-button>
Three
</calcite-label>
</calcite-radio-button-group>
<calcite-radio-button-group
layout="horizontal"
name="validation"
required
scale="m"
status="invalid"
validation-icon
validation-message="Please select an option."
>
<calcite-label layout="inline" scale="m">
<calcite-radio-button value="one" scale="m"></calcite-radio-button>
One
</calcite-label>
<calcite-label layout="inline" scale="m">
<calcite-radio-button value="two" scale="m"></calcite-radio-button>
Two
</calcite-label>
<calcite-label layout="inline" scale="m">
<calcite-radio-button value="three" scale="m"></calcite-radio-button>
Three
</calcite-label>
</calcite-radio-button-group>
<calcite-radio-button-group
layout="horizontal"
name="validation"
required
scale="l"
status="invalid"
validation-icon
validation-message="Please select an option."
>
<calcite-label layout="inline" scale="l">
<calcite-radio-button value="one" scale="l"></calcite-radio-button>
One
</calcite-label>
<calcite-label layout="inline" scale="l">
<calcite-radio-button value="two" scale="l"></calcite-radio-button>
Two
</calcite-label>
<calcite-label layout="inline" scale="l">
<calcite-radio-button value="three" scale="l"></calcite-radio-button>
Three
</calcite-label>
</calcite-radio-button-group>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import {
Watch,
} from "@stencil/core";
import { createObserver } from "../../utils/observers";
import { Layout, Scale } from "../interfaces";
import { Layout, Scale, Status } from "../interfaces";
import {
componentFocusable,
LoadableComponent,
setComponentLoaded,
setUpLoadableComponent,
} from "../../utils/loadable";
import { Validation } from "../functional/Validation";
import { CSS } from "./resources";

/**
* @slot - A slot for adding `calcite-radio-button`s.
Expand Down Expand Up @@ -76,6 +78,15 @@ export class RadioButtonGroup implements LoadableComponent {
/** Specifies the size of the component. */
@Prop({ reflect: true }) scale: Scale = "m";

/** Specifies the status of the validation message. */
@Prop({ reflect: true }) status: Status = "idle";

/** Specifies the validation message to display under the component. */
@Prop() validationMessage: string;

/** Specifies the validation icon to display under the component. */
@Prop({ reflect: true }) validationIcon: string | boolean;

@Watch("scale")
onScaleChange(): void {
this.passPropsToRadioButtons();
Expand Down Expand Up @@ -191,7 +202,17 @@ export class RadioButtonGroup implements LoadableComponent {
render(): VNode {
return (
<Host role="radiogroup">
<slot />
<div class={CSS.itemWrapper}>
<slot />
</div>
{this.validationMessage ? (
<Validation
icon={this.validationIcon}
message={this.validationMessage}
scale={this.scale}
status={this.status}
/>
) : null}
</Host>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CSS = {
itemWrapper: "item-wrapper",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CSS = {
itemWrapper: "item-wrapper",
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ describe("calcite-segmented-control", () => {
propertyName: "width",
defaultValue: "auto",
},
{
propertyName: "status",
defaultValue: "idle",
},
{
propertyName: "validationIcon",
defaultValue: undefined,
},
{
propertyName: "validationMessage",
defaultValue: undefined,
},
]);
});

Expand All @@ -52,6 +64,14 @@ describe("calcite-segmented-control", () => {
propertyName: "width",
value: "auto",
},
{
propertyName: "status",
value: "invalid",
},
{
propertyName: "validationIcon",
value: true,
},
]);
});

Expand Down
Loading

0 comments on commit d4c5efc

Please sign in to comment.