From b59629a02aac013495b9bcb5a7b82d68f304bb90 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:29:23 -0500 Subject: [PATCH 01/16] Add error state to nimble-checkbox --- .../nimble-components/src/checkbox/index.ts | 9 ++- .../nimble-components/src/checkbox/styles.ts | 68 ++++++++++++------- .../src/checkbox/template.ts | 41 ++++++----- .../src/nimble/checkbox/checkbox.stories.ts | 20 +++++- 4 files changed, 95 insertions(+), 43 deletions(-) diff --git a/packages/nimble-components/src/checkbox/index.ts b/packages/nimble-components/src/checkbox/index.ts index 8a1d80626f..225b985075 100644 --- a/packages/nimble-components/src/checkbox/index.ts +++ b/packages/nimble-components/src/checkbox/index.ts @@ -7,6 +7,7 @@ import { import { check16X16, minus16X16 } from '@ni/nimble-tokens/dist/icons/js'; import { styles } from './styles'; import { template } from './template'; +import type { ErrorPattern } from '../patterns/error/types'; declare global { interface HTMLElementTagNameMap { @@ -17,7 +18,7 @@ declare global { /** * A nimble-styled checkbox control. */ -export class Checkbox extends FoundationCheckbox { +export class Checkbox extends FoundationCheckbox implements ErrorPattern { /** * @public * @remarks @@ -26,6 +27,12 @@ export class Checkbox extends FoundationCheckbox { @attr({ attribute: 'tabindex', converter: nullableNumberConverter }) public override tabIndex!: number; + @attr({ attribute: 'error-text' }) + public errorText?: string; + + @attr({ attribute: 'error-visible', mode: 'boolean' }) + public errorVisible = false; + /** * @internal */ diff --git a/packages/nimble-components/src/checkbox/styles.ts b/packages/nimble-components/src/checkbox/styles.ts index 8da0c9e22a..0d4e57185d 100644 --- a/packages/nimble-components/src/checkbox/styles.ts +++ b/packages/nimble-components/src/checkbox/styles.ts @@ -12,29 +12,40 @@ import { iconSize, borderWidth, smallDelay, - bodyFont + bodyFont, + smallPadding, + mediumPadding, + bodyFontLineHeight } from '../theme-provider/design-tokens'; import { userSelectNone } from '../utilities/style/user-select'; +import { styles as errorStyles } from '../patterns/error/styles'; export const styles = css` ${display('inline-flex')} + ${errorStyles} :host { font: ${bodyFont}; - align-items: center; - cursor: pointer; outline: none; ${userSelectNone} + min-height: ${controlHeight}; + align-items: center; } - :host([disabled]) { - cursor: default; + .container { + position: relative; + display: grid; + grid-template-columns: auto auto 1fr auto; + grid-template-rows: ${bodyFontLineHeight} auto; + align-items: center; + width: 100%; + padding: 0px ${smallPadding}; } .control { - width: calc(${controlHeight} / 2); - height: calc(${controlHeight} / 2); - flex-shrink: 0; + cursor: pointer; + width: ${iconSize}; + height: ${iconSize}; border: ${borderWidth} solid ${borderColor}; padding: 2px; display: inline-flex; @@ -48,6 +59,8 @@ export const styles = css` */ '' } line-height: 0; + grid-column: 1; + grid-row: 1; } @media (prefers-reduced-motion) { @@ -56,30 +69,35 @@ export const styles = css` } } + :host(${focusVisible}) .control { + border-color: ${borderHoverColor}; + outline: 2px solid ${borderHoverColor}; + outline-offset: 1px; + } + :host([disabled]) .control { + cursor: default; background-color: rgba(${borderRgbPartialColor}, 0.1); border-color: rgba(${borderRgbPartialColor}, 0.2); } - :host(:not([disabled]):not(:active):hover) .control { + :host(:not([disabled]):not(:active)) .control:hover, + :host(:not([disabled]):not(:active)) .control:has(+ .label:hover) { border-color: ${borderHoverColor}; box-shadow: 0px 0px 0px ${borderWidth} ${borderHoverColor} inset; } - :host(${focusVisible}) .control { - border-color: ${borderHoverColor}; - outline: 2px solid ${borderHoverColor}; - outline-offset: 1px; - } - .label { + cursor: pointer; font: inherit; color: ${bodyFontColor}; - padding-left: 1ch; - cursor: inherit; + padding-left: ${mediumPadding}; + grid-column: 2; + grid-row: 1 / span 2; } :host([disabled]) .label { + cursor: default; color: ${bodyDisabledFontColor}; } @@ -92,16 +110,13 @@ export const styles = css` height: ${iconSize}; width: ${iconSize}; overflow: visible; + fill: ${borderColor}; } :host(.checked:not(.indeterminate)) slot[name='checked-indicator'] { display: contents; } - slot[name='checked-indicator'] svg { - fill: ${borderColor}; - } - :host([disabled]) slot[name='checked-indicator'] svg { fill: rgba(${borderRgbPartialColor}, 0.3); } @@ -110,17 +125,20 @@ export const styles = css` height: ${iconSize}; width: ${iconSize}; overflow: visible; + fill: ${borderColor}; } :host(.indeterminate) slot[name='indeterminate-indicator'] { display: contents; } - slot[name='indeterminate-indicator'] svg { - fill: ${borderColor}; - } - :host([disabled]) slot[name='indeterminate-indicator'] svg { fill: rgba(${borderRgbPartialColor}, 0.3); } + + .error-icon { + grid-column: 4; + grid-row: 1; + margin-left: ${smallPadding}; + } `; diff --git a/packages/nimble-components/src/checkbox/template.ts b/packages/nimble-components/src/checkbox/template.ts index b5865a3b3e..726cd1f9e6 100644 --- a/packages/nimble-components/src/checkbox/template.ts +++ b/packages/nimble-components/src/checkbox/template.ts @@ -4,6 +4,8 @@ import type { FoundationElementTemplate } from '@microsoft/fast-foundation'; import type { Checkbox } from '.'; +import { iconExclamationMarkTag } from '../icons/exclamation-mark'; +import { errorTextTemplate } from '../patterns/error/template'; export const template: FoundationElementTemplate< ViewTemplate, @@ -17,24 +19,31 @@ CheckboxOptions aria-readonly="${x => x.readOnly}" tabindex="${x => x.resolvedTabindex}" @keypress="${(x, c) => x.keypressHandler(c.event as KeyboardEvent)}" - @click="${(x, c) => x.clickHandler(c.event as MouseEvent)}" class="${x => (x.readOnly ? 'readonly' : '')} ${x => (x.checked ? 'checked' : '')} ${x => (x.indeterminate ? 'indeterminate' : '')}" > -
- - ${definition.checkedIndicator || ''} - - - ${definition.indeterminateIndicator || ''} - -
- + <${iconExclamationMarkTag} + severity="error" + class="error-icon" + > + ${errorTextTemplate} + -`; +`; \ No newline at end of file diff --git a/packages/storybook/src/nimble/checkbox/checkbox.stories.ts b/packages/storybook/src/nimble/checkbox/checkbox.stories.ts index ae7311c139..7bf0c08cfa 100644 --- a/packages/storybook/src/nimble/checkbox/checkbox.stories.ts +++ b/packages/storybook/src/nimble/checkbox/checkbox.stories.ts @@ -6,6 +6,8 @@ import { apiCategory, createUserSelectedThemeStory, disabledDescription, + errorTextDescription, + errorVisibleDescription, slottedLabelDescription } from '../../utilities/storybook'; @@ -16,6 +18,8 @@ interface CheckboxArgs { indeterminate: boolean; disabled: boolean; change: undefined; + errorVisible: boolean; + errorText: string; } const metadata: Meta = { @@ -31,6 +35,8 @@ const metadata: Meta = { ?checked="${x => x.checked}" ?disabled="${x => x.disabled}" :indeterminate="${x => x.indeterminate}" + ?error-visible="${x => x.errorVisible}" + error-text="${x => x.errorText}" > ${x => x.label} @@ -70,13 +76,25 @@ The \`indeterminate\` state is not automatically changed when the user interacti 'Event emitted when the user checks or unchecks the checkbox.', table: { category: apiCategory.events }, control: false + }, + errorText: { + name: 'error-text', + description: errorTextDescription, + table: { category: apiCategory.attributes } + }, + errorVisible: { + name: 'error-visible', + description: errorVisibleDescription, + table: { category: apiCategory.attributes } } }, args: { label: 'Checkbox label', checked: false, indeterminate: false, - disabled: false + disabled: false, + errorVisible: false, + errorText: 'Value is invalid', } }; From df7163e31eeaba4602e7bbbb1a5f2141521fe136 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:29:51 -0500 Subject: [PATCH 02/16] Change files --- ...le-components-d948f8c3-7e90-4ec6-b1f3-468c20ad234d.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@ni-nimble-components-d948f8c3-7e90-4ec6-b1f3-468c20ad234d.json diff --git a/change/@ni-nimble-components-d948f8c3-7e90-4ec6-b1f3-468c20ad234d.json b/change/@ni-nimble-components-d948f8c3-7e90-4ec6-b1f3-468c20ad234d.json new file mode 100644 index 0000000000..6efd13a18f --- /dev/null +++ b/change/@ni-nimble-components-d948f8c3-7e90-4ec6-b1f3-468c20ad234d.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add error state to nimble-checkbox", + "packageName": "@ni/nimble-components", + "email": "20542556+mollykreis@users.noreply.github.com", + "dependentChangeType": "patch" +} From e7b3b17894318a527b8c3e8af6b996bef9b6f5ab Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:20:25 -0500 Subject: [PATCH 03/16] format --- packages/nimble-components/src/checkbox/template.ts | 2 +- packages/storybook/src/nimble/checkbox/checkbox.stories.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nimble-components/src/checkbox/template.ts b/packages/nimble-components/src/checkbox/template.ts index 726cd1f9e6..35c81af11e 100644 --- a/packages/nimble-components/src/checkbox/template.ts +++ b/packages/nimble-components/src/checkbox/template.ts @@ -46,4 +46,4 @@ CheckboxOptions ${errorTextTemplate} -`; \ No newline at end of file +`; diff --git a/packages/storybook/src/nimble/checkbox/checkbox.stories.ts b/packages/storybook/src/nimble/checkbox/checkbox.stories.ts index 7bf0c08cfa..acc4ab0744 100644 --- a/packages/storybook/src/nimble/checkbox/checkbox.stories.ts +++ b/packages/storybook/src/nimble/checkbox/checkbox.stories.ts @@ -94,7 +94,7 @@ The \`indeterminate\` state is not automatically changed when the user interacti indeterminate: false, disabled: false, errorVisible: false, - errorText: 'Value is invalid', + errorText: 'Value is invalid' } }; From e35344d39f80a7e21e8c32ed012127e68bde3caa Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:29:53 -0500 Subject: [PATCH 04/16] make entire checkbox clickable --- packages/nimble-components/src/checkbox/styles.ts | 3 +-- packages/nimble-components/src/checkbox/template.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/nimble-components/src/checkbox/styles.ts b/packages/nimble-components/src/checkbox/styles.ts index 0d4e57185d..d9a86dc353 100644 --- a/packages/nimble-components/src/checkbox/styles.ts +++ b/packages/nimble-components/src/checkbox/styles.ts @@ -81,8 +81,7 @@ export const styles = css` border-color: rgba(${borderRgbPartialColor}, 0.2); } - :host(:not([disabled]):not(:active)) .control:hover, - :host(:not([disabled]):not(:active)) .control:has(+ .label:hover) { + :host(:not([disabled]):not(:active):hover) .control { border-color: ${borderHoverColor}; box-shadow: 0px 0px 0px ${borderWidth} ${borderHoverColor} inset; } diff --git a/packages/nimble-components/src/checkbox/template.ts b/packages/nimble-components/src/checkbox/template.ts index 35c81af11e..9ce6006190 100644 --- a/packages/nimble-components/src/checkbox/template.ts +++ b/packages/nimble-components/src/checkbox/template.ts @@ -19,10 +19,11 @@ CheckboxOptions aria-readonly="${x => x.readOnly}" tabindex="${x => x.resolvedTabindex}" @keypress="${(x, c) => x.keypressHandler(c.event as KeyboardEvent)}" + @click="${(x, c) => x.clickHandler(c.event as MouseEvent)}" class="${x => (x.readOnly ? 'readonly' : '')} ${x => (x.checked ? 'checked' : '')} ${x => (x.indeterminate ? 'indeterminate' : '')}" >
-
+
${definition.checkedIndicator || ''} @@ -32,7 +33,6 @@ CheckboxOptions