Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error state to nimble-checkbox #2478

Merged
merged 15 commits into from
Nov 22, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
"comment": "Add error state to nimble-checkbox; Updating styling on nimble-checkbox, including correcting the default height of the checkbox and fixing the layout when the label wraps",
"packageName": "@ni/nimble-components",
"email": "20542556+mollykreis@users.noreply.github.com",
"dependentChangeType": "patch"
}
9 changes: 8 additions & 1 deletion packages/nimble-components/src/checkbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
*/
Expand Down
39 changes: 30 additions & 9 deletions packages/nimble-components/src/checkbox/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,43 @@ 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};
}

:host([disabled]) {
cursor: default;
}

.container {
position: relative;
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-rows: ${bodyFontLineHeight} auto;
align-items: center;
width: 100%;
}

.control {
width: calc(${controlHeight} / 2);
height: calc(${controlHeight} / 2);
flex-shrink: 0;
width: ${iconSize};
height: ${iconSize};
border: ${borderWidth} solid ${borderColor};
padding: 2px;
display: inline-flex;
Expand All @@ -48,6 +62,8 @@ export const styles = css`
*/ ''
}
line-height: 0;
grid-column: 1;
grid-row: 1;
}

@media (prefers-reduced-motion) {
Expand Down Expand Up @@ -75,7 +91,9 @@ export const styles = css`
.label {
font: inherit;
color: ${bodyFontColor};
padding-left: 1ch;
padding-left: ${mediumPadding};
grid-column: 2;
grid-row: 1 / span 2;
cursor: inherit;
}

Expand All @@ -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 {
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
fill: ${borderColor};
}

:host([disabled]) slot[name='checked-indicator'] svg {
fill: rgba(${borderRgbPartialColor}, 0.3);
}
Expand All @@ -123,4 +138,10 @@ export const styles = css`
:host([disabled]) slot[name='indeterminate-indicator'] svg {
fill: rgba(${borderRgbPartialColor}, 0.3);
}

.error-icon {
grid-column: 3;
grid-row: 1;
margin: 0px ${smallPadding};
}
`;
37 changes: 23 additions & 14 deletions packages/nimble-components/src/checkbox/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Checkbox>,
Expand All @@ -20,21 +22,28 @@ CheckboxOptions
@click="${(x, c) => x.clickHandler(c.event as MouseEvent)}"
class="${x => (x.readOnly ? 'readonly' : '')} ${x => (x.checked ? 'checked' : '')} ${x => (x.indeterminate ? 'indeterminate' : '')}"
>
<div part="control" class="control">
<slot name="checked-indicator">
${definition.checkedIndicator || ''}
</slot>
<slot name="indeterminate-indicator">
${definition.indeterminateIndicator || ''}
</slot>
</div>
<label
part="label"
class="${x => (x.defaultSlottedNodes?.length
<div part="container" class="container">
<div part="control" class="control">
<slot name="checked-indicator">
${definition.checkedIndicator || ''}
</slot>
<slot name="indeterminate-indicator">
${definition.indeterminateIndicator || ''}
</slot>
</div>
<label
part="label"
class="${x => (x.defaultSlottedNodes?.length
? 'label'
: 'label label__hidden')}"
>
<slot ${slotted('defaultSlottedNodes')}></slot>
</label>
>
<slot ${slotted('defaultSlottedNodes')}></slot>
</label>
<${iconExclamationMarkTag}
severity="error"
class="error-icon"
></${iconExclamationMarkTag}>
${errorTextTemplate}
</div>
</template>
`;
1 change: 1 addition & 0 deletions packages/nimble-components/src/table/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const styles = css`

.header-row-action-container {
display: flex;
align-items: center;
}

.checkbox-container {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import type { StoryFn, Meta } from '@storybook/html';
import { html, ViewTemplate } from '@microsoft/fast-element';
import { standardPadding } from '../../../../nimble-components/src/theme-provider/design-tokens';
import { checkboxTag } from '../../../../nimble-components/src/checkbox';
import {
createMatrix,
sharedMatrixParameters,
createMatrixThemeStory
} from '../../utilities/matrix';
import { disabledStates, DisabledState } from '../../utilities/states';
import {
disabledStates,
DisabledState,
errorStates,
ErrorState
} from '../../utilities/states';
import { createStory } from '../../utilities/storybook';
import { hiddenWrapper } from '../../utilities/hidden';
import { textCustomizationWrapper } from '../../utilities/text-customization';
import { loremIpsum } from '../../utilities/lorem-ipsum';

const checkedStates = [
['Checked', true],
Expand All @@ -23,6 +30,9 @@ const indeterminateStates = [
] as const;
type IndeterminateState = (typeof indeterminateStates)[number];

const wrappingStates = [[''], [loremIpsum]] as const;
type WrappingState = (typeof wrappingStates)[number];

const metadata: Meta = {
title: 'Tests/Checkbox',
parameters: {
Expand All @@ -35,20 +45,27 @@ export default metadata;
const component = (
[disabledName, disabled]: DisabledState,
[checkedName, checked]: CheckedState,
[indeterminateName, indeterminate]: IndeterminateState
[indeterminateName, indeterminate]: IndeterminateState,
[errorName, errorVisible, errorText]: ErrorState,
[extraText]: WrappingState
): ViewTemplate => html`<${checkboxTag}
?checked="${() => checked}"
?disabled="${() => disabled}"
?error-visible="${() => errorVisible}"
error-text="${() => errorText}"
:indeterminate="${() => indeterminate}"
style="width: 350px; margin: var(${standardPadding.cssCustomProperty});"
>
${checkedName} ${indeterminateName} ${disabledName}
${checkedName} ${indeterminateName} ${disabledName} ${errorName} ${extraText}
</${checkboxTag}>`;

export const checkboxThemeMatrix: StoryFn = createMatrixThemeStory(
createMatrix(component, [
disabledStates,
checkedStates,
indeterminateStates
indeterminateStates,
errorStates,
wrappingStates
])
);

Expand Down
20 changes: 19 additions & 1 deletion packages/storybook/src/nimble/checkbox/checkbox.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
apiCategory,
createUserSelectedThemeStory,
disabledDescription,
errorTextDescription,
errorVisibleDescription,
slottedLabelDescription
} from '../../utilities/storybook';

Expand All @@ -16,6 +18,8 @@ interface CheckboxArgs {
indeterminate: boolean;
disabled: boolean;
change: undefined;
errorVisible: boolean;
errorText: string;
}

const metadata: Meta<CheckboxArgs> = {
Expand All @@ -31,6 +35,8 @@ const metadata: Meta<CheckboxArgs> = {
?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}
</${checkboxTag}>
Expand Down Expand Up @@ -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'
}
};

Expand Down