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

feat(checkbox): slug #11215

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/**
* @license
*
* Copyright IBM Corp. 2019, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { classMap } from 'lit/directives/class-map.js';
import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';
import { prefix } from '../../globals/settings';
import WarningFilled16 from '@carbon/icons/lib/warning--filled/16';
import WarningAltFilled16 from '@carbon/icons/lib/warning--alt--filled/16';
import CDSCheckbox from './checkbox';
import styles from './checkbox.scss';
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';

/**
* Check box.
*
* @element cds-checkbox
* @fires cds-checkbox-changed - The custom event fired after this changebox changes its checked state.
* @csspart input The checkbox.
* @csspart label The label.
*/
@customElement(`${prefix}-checkbox-group`)
class CDSCheckboxGroup extends LitElement {
/**
* fieldset `aria-labelledby`
*/
@property({ type: String, reflect: true, attribute: 'aria-labelledby' })
ariaLabelledBy;

/**
* Specify whether the form group is currently disabled
*/
@property({ type: Boolean })
disabled;

/**
* Provide text for the form group for additional help
*/
@property({ type: String, reflect: true, attribute: 'helper-text' })
helperText;

/**
* Specify whether the form group is currently invalid
*/
@property({ type: Boolean, attribute: 'invalid' })
invalid;

/**
* Provide the text that is displayed when the form group is in an invalid state
*/
@property({ type: String, reflect: true, attribute: 'invalid-text' })
invalidText;

/**
* Provide id for the fieldset <legend> which corresponds to the fieldset
* `aria-labelledby`
*/
@property({ type: String, reflect: true, attribute: 'legend-id' })
legendId;

/**
* Provide the text to be rendered inside of the fieldset <legend>
*/
@property({ type: String, reflect: true, attribute: 'legend-text' })
legendText;

/**
* Whether the CheckboxGroup should be read-only
*/
@property({ type: Boolean, reflect: true })
readonly = false;

/**
* Specify whether the form group is currently in warning state
*/
@property({ type: Boolean, reflect: true })
warn = false;

/**
* Provide the text that is displayed when the form group is in warning state
*/
@property({ type: String, reflect: true, attribute: 'warn-text' })
warnText = '';

/*
* Handles `slotchange` event.
*/
protected _handleSlotChange({ target }: Event) {
const hasContent = (target as HTMLSlotElement)
.assignedNodes()
.filter((elem) =>
(elem as HTMLElement).matches !== undefined
? (elem as HTMLElement).matches(
(this.constructor as typeof CDSCheckboxGroup).slugItem
)
: false
);

this._hasSlug = Boolean(hasContent);
(hasContent[0] as HTMLElement).setAttribute('size', 'mini');
this.requestUpdate();
}

/**
* `true` if there is a slug.
*/
protected _hasSlug = false;

updated(changedProperties) {
const { selectorCheckbox } = this.constructor as typeof CDSCheckboxGroup;
const checkboxes = this.querySelectorAll(selectorCheckbox);
['disabled', 'readonly'].forEach((name) => {
if (changedProperties.has(name)) {
const { [name as keyof CDSCheckboxGroup]: value } = this;
// Propagate the property to descendants until `:host-context()` gets supported in all major browsers
checkboxes.forEach((elem) => {
(elem as CDSCheckbox)[name] = value;
});
}
});
}

render() {
const {
ariaLabelledBy,
disabled,
helperText,
invalid,
invalidText,
legendId,
legendText,
readonly,
warn,
warnText,
_hasSlug: hasSlug,
_handleSlotChange: handleSlotChange,
} = this;

const showWarning = !readonly && !invalid && warn;
const showHelper = !invalid && !warn;

const checkboxGroupInstanceId = Math.random().toString(16).slice(2);

const helperId = !helperText
? undefined
: `checkbox-group-helper-text-${checkboxGroupInstanceId}`;

const helper = helperText
? html` <div id="${helperId}" class="${prefix}--form__helper-text">
${helperText}
</div>`
: null;

const fieldsetClasses = classMap({
[`${prefix}--checkbox-group`]: true,
[`${prefix}--checkbox-group--readonly`]: readonly,
[`${prefix}--checkbox-group--invalid`]: !readonly && invalid,
[`${prefix}--checkbox-group--warning`]: showWarning,
[`${prefix}--checkbox-group--slug`]: hasSlug,
});

return html`
<fieldset
class="${fieldsetClasses}"
?data-invalid=${invalid}
?disabled=${disabled}
aria-readonly=${readonly}
?aria-labelledby=${ariaLabelledBy || legendId}
?aria-describedby=${!invalid && !warn && helper ? helperId : undefined}>
<legend class="${prefix}--label" id=${legendId || ariaLabelledBy}>
${legendText}
<slot name="slug" @slotchange="${handleSlotChange}"></slot>
</legend>
<slot></slot>
<div class="${prefix}--checkbox-group__validation-msg">
${!readonly && invalid
? html`
${WarningFilled16({
class: `${prefix}--checkbox__invalid-icon`,
})}
<div class="${prefix}--form-requirement">${invalidText}</div>
`
: null}
${showWarning
? html`
${WarningAltFilled16({
class: `${prefix}--checkbox__invalid-icon ${prefix}--checkbox__invalid-icon--warning`,
})}
<div class="${prefix}--form-requirement">${warnText}</div>
`
: null}
</div>
${showHelper ? helper : null}
</fieldset>
`;
}

/**
* A selector that will return the checkboxes.
*/
static get selectorCheckbox() {
return `${prefix}-checkbox`;
}

/**
* A selector that will return the slug item.
*/
static get slugItem() {
return `${prefix}-slug`;
}

static shadowRootOptions = {
...LitElement.shadowRootOptions,
delegatesFocus: true,
};
static styles = styles; // `styles` here is a `CSSResult` generated by custom WebPack loader
}

export default CDSCheckboxGroup;
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ import '@carbon/web-components/es/components/checkbox/index.js';
<cds-checkbox label-text="Lorem Ipsum"></cds-checkbox>
```

Use `cds-checkbox-group` when handling multiple checkboxes

```html
<cds-checkbox-group legend-text="Group label">
<cds-checkbox label-text="Lorem Ipsum 0"></cds-checkbox>
<cds-checkbox label-text="Lorem Ipsum 1"></cds-checkbox>
</cds-checkbox-group>
```

## `<cds-checkbox>` attributes, properties and events

Unlike regular checkbox, `<cds-checkbox>` does _not_ fire `change` event. Please
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { html } from 'lit';
import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';
import { ifDefined } from 'lit/directives/if-defined.js';

Check warning on line 13 in packages/carbon-web-components/src/components/checkbox/checkbox-story.ts

View workflow job for this annotation

GitHub Actions / check-packages

'ifDefined' is defined but never used

Check warning on line 13 in packages/carbon-web-components/src/components/checkbox/checkbox-story.ts

View workflow job for this annotation

GitHub Actions / check-packages

'ifDefined' is defined but never used
import { prefix } from '../../globals/settings';
import textNullable from '../../../.storybook/knob-text-nullable';
import './index';
Expand All @@ -20,11 +20,10 @@

export const Default = () => {
return html`
<fieldset class="${prefix}--fieldset">
<legend class="${prefix}--label">Group label</legend>
<cds-checkbox-group legend-text="Group label">
<cds-checkbox label-text="${checkboxLabel}"></cds-checkbox>
<cds-checkbox label-text="${checkboxLabel}"></cds-checkbox>
</fieldset>
</cds-checkbox-group>
`;
};

Expand All @@ -39,52 +38,81 @@
`;
};

export const Single = () => {
return html`
<cds-checkbox
label-text="${checkboxLabel}"
helper-text="Helper text goes here"></cds-checkbox>
<br /><br />
<cds-checkbox
label-text="${checkboxLabel}"
invalid
invalid-text="Invalid test goes here"></cds-checkbox>
<br /><br />
<cds-checkbox
label-text="${checkboxLabel}"
warn
warn-text="Warning test goes here"></cds-checkbox>
<br /><br />
<cds-checkbox label-text="${checkboxLabel}" readonly></cds-checkbox>
`;
};

export const Playground = (args) => {
const {
checked,
disabled,
hideLabel,
indeterminate,
labelText = checkboxLabel,
readonly,
title,
onChange,
helperText,
invalid,
invalidText,
legendText,
warn,
warnText,
} = args?.[`${prefix}-checkbox`] ?? {};
return html`
<fieldset class="${prefix}--fieldset">
<legend class="${prefix}--label">Group label</legend>
<cds-checkbox-group
helper-text="${helperText}"
?disabled="${disabled}"
?invalid="${invalid}"
invalid-text="${invalidText}"
legend-text="${legendText}"
?readonly="${readonly}"
?warn="${warn}"
warn-text="${warnText}">
<cds-checkbox
?checked="${checked}"
?disabled="${disabled}"
?hide-label="${hideLabel}"
?indeterminate="${indeterminate}"
label-text="${ifDefined(labelText)}"
?readonly="${readonly}"
title="${ifDefined(title)}"
checked
label-text="Checkbox label"
@cds-checkbox-changed="${onChange}"></cds-checkbox>
<cds-checkbox
?checked="${checked}"
?disabled="${disabled}"
?hide-label="${hideLabel}"
?indeterminate="${indeterminate}"
label-text="${ifDefined(labelText)}"
?readonly="${readonly}"
title="${ifDefined(title)}"
label-text="Checkbox label"
@cds-checkbox-changed="${onChange}"></cds-checkbox>
</fieldset>
<cds-checkbox
disabled
label-text="Checkbox label"
@cds-checkbox-changed="${onChange}"></cds-checkbox>
</cds-checkbox-group>
`;
};

Playground.parameters = {
knobs: {
[`${prefix}-checkbox`]: () => ({
checked: boolean('Checked (checked)', false),
onChange: action(`${prefix}-checkbox-changed`),
disabled: boolean('Disabled (disabled)', false),
hideLabel: boolean('Hide label (hide-label)', false),
indeterminate: boolean('Indeterminate (indeterminate)', false),
helperText: textNullable(
'Helper text (helper-text)',
'Helper text goes here'
),
invalid: boolean('Invalid (invalid)', false),
invalidText: textNullable(
'Invalid text (invalid-text)',
'Invalid message goes here'
),
legendText: textNullable('Legend text (legend-text)', 'Group label'),
readonly: boolean('Read only (readonly)', false),
title: textNullable('Title (title)', ''),
onChange: action(`${prefix}-checkbox-changed`),
warn: boolean('Warn (warn)', false),
warnText: textNullable('Warn text (warn-text)', 'Warn message goes here'),
}),
},
};
Expand Down
Loading
Loading