Skip to content

Commit

Permalink
feat: update label to use element internals custom states for styling (
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdholt authored Jun 18, 2024
1 parent 14ff3fa commit a3b7e64
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "update label to use element internals custom states for styling",
"packageName": "@fluentui/web-components",
"email": "13071055+chrisdholt@users.noreply.github.com",
"dependentChangeType": "patch"
}
9 changes: 8 additions & 1 deletion packages/web-components/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -2146,9 +2146,14 @@ export const ImageTemplate: ElementViewTemplate<Image_2>;
// @public
export class Label extends FASTElement {
disabled: boolean;
disabledChanged(prev: boolean | undefined, next: boolean | undefined): void;
// @internal
elementInternals: ElementInternals;
required: boolean;
size?: LabelSize;
sizeChanged(prev: LabelSize | undefined, next: LabelSize | undefined): void;
weight?: LabelWeight;
weightChanged(prev: LabelWeight | undefined, next: LabelWeight | undefined): void;
}

// @public
Expand Down Expand Up @@ -2883,9 +2888,11 @@ export const spacingVerticalXXXL = "var(--spacingVerticalXXXL)";
export class Spinner extends FASTElement {
constructor();
appearance?: SpinnerAppearance;
appearanceChanged(prev: SpinnerAppearance | undefined, next: SpinnerAppearance | undefined): void;
// @internal
protected elementInternals: ElementInternals;
elementInternals: ElementInternals;
size?: SpinnerSize;
sizeChanged(prev: SpinnerSize | undefined, next: SpinnerSize | undefined): void;
}

// @public
Expand Down
55 changes: 9 additions & 46 deletions packages/web-components/src/label/label.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,31 @@ test.describe('Label', () => {
await page.close();
});

test('should set default attribute values', async () => {
await expect(element).toHaveAttribute('size', 'medium');
await expect(element).toHaveJSProperty('size', 'medium');

await expect(element).toHaveAttribute('weight', 'regular');
await expect(element).toHaveJSProperty('weight', 'regular');
});

test('should reflect size attribute', async () => {
await element.evaluate((node: Label) => {
node.size = 'small';
});

await expect(element).toHaveAttribute('size', 'small');
await expect(element).toHaveJSProperty('size', 'small');
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('small'))).toBe(true);

await element.evaluate((node: Label) => {
node.size = 'medium';
});

await expect(element).toHaveAttribute('size', 'medium');
await expect(element).toHaveJSProperty('size', 'medium');
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('small'))).toBe(false);
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('medium'))).toBe(true);

await element.evaluate((node: Label) => {
node.size = 'large';
});
await expect(element).toHaveAttribute('size', 'large');
await expect(element).toHaveJSProperty('size', 'large');
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('medium'))).toBe(false);
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('large'))).toBe(true);
});

test('should reflect weight attribute', async () => {
Expand All @@ -57,12 +54,15 @@ test.describe('Label', () => {
});
await expect(element).toHaveAttribute('weight', 'regular');
await expect(element).toHaveJSProperty('weight', 'regular');
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('regular'))).toBe(true);

await element.evaluate((node: Label) => {
node.weight = 'semibold';
});
await expect(element).toHaveAttribute('weight', 'semibold');
await expect(element).toHaveJSProperty('weight', 'semibold');
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('regular'))).toBe(false);
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('semibold'))).toBe(true);
});

test('should reflect disabled attribute', async () => {
Expand All @@ -74,6 +74,7 @@ test.describe('Label', () => {

await expect(element).toHaveAttribute('disabled', '');
await expect(element).toHaveJSProperty('disabled', true);
expect(await element.evaluate((node: Label) => node.elementInternals.states.has('disabled'))).toBe(true);
});

test('should reflect required attribute and show asterisk', async () => {
Expand Down Expand Up @@ -102,42 +103,4 @@ test.describe('Label', () => {
await expect(element).toHaveJSProperty('required', false);
await expect(asterisk).toBeHidden();
});

test('should reflect changes in size, weight, disabled, and required attributes', async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
<fluent-label size="medium" weight="regular">Label</fluent-label>
`;
});
await expect(element).toHaveAttribute('size', 'medium');
await expect(element).toHaveJSProperty('size', 'medium');

await expect(element).toHaveAttribute('weight', 'regular');
await expect(element).toHaveJSProperty('weight', 'regular');

await expect(element).not.toHaveAttribute('disabled', '');
await expect(element).toHaveJSProperty('disabled', false);

await expect(element).not.toHaveAttribute('required', '');
await expect(element).toHaveJSProperty('required', false);

await element.evaluate((node: Label) => {
node.size = 'large';
node.weight = 'semibold';
node.disabled = true;
node.required = true;
});

await expect(element).toHaveAttribute('size', 'large');
await expect(element).toHaveJSProperty('size', 'large');

await expect(element).toHaveAttribute('weight', 'semibold');
await expect(element).toHaveJSProperty('weight', 'semibold');

await expect(element).toHaveAttribute('disabled', '');
await expect(element).toHaveJSProperty('disabled', true);

await expect(element).toHaveAttribute('required', '');
await expect(element).toHaveJSProperty('required', true);
});
});
13 changes: 7 additions & 6 deletions packages/web-components/src/label/label.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
lineHeightBase400,
spacingHorizontalXS,
} from '../theme/design-tokens.js';
import { largeState, smallState } from '../styles/states/index.js';

/** Label styles
* @public
Expand All @@ -37,23 +38,23 @@ export const styles = css`
margin-inline-start: ${spacingHorizontalXS};
}
:host([size='small']) {
:host(${smallState}) {
font-size: ${fontSizeBase200};
line-height: ${lineHeightBase200};
}
:host([size='large']) {
:host(${largeState}) {
font-size: ${fontSizeBase400};
line-height: ${lineHeightBase400};
}
:host([size='large']),
:host([weight='semibold']) {
:host(${largeState}),
:host(:is([state--semibold], :state(semibold))) {
font-weight: ${fontWeightSemibold};
}
:host([disabled]),
:host([disabled]) .asterisk {
:host(:is([state--disabled], :state(disabled))),
:host(:is([state--disabled], :state(disabled))) .asterisk {
color: ${colorNeutralForegroundDisabled};
}
`;
45 changes: 45 additions & 0 deletions packages/web-components/src/label/label.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { attr, FASTElement } from '@microsoft/fast-element';
import { toggleState } from '../utils/element-internals.js';
import { LabelSize, LabelWeight } from './label.options.js';

/**
* The base class used for constructing a fluent-label custom element
* @public
*/
export class Label extends FASTElement {
/**
* The internal {@link https://developer.mozilla.org/docs/Web/API/ElementInternals | `ElementInternals`} instance for the component.
*
* @internal
*/
public elementInternals: ElementInternals = this.attachInternals();

/**
* Specifies font size of a label
*
Expand All @@ -16,6 +24,20 @@ export class Label extends FASTElement {
@attr
public size?: LabelSize;

/**
* Handles changes to size attribute custom states
* @param prev - the previous state
* @param next - the next state
*/
public sizeChanged(prev: LabelSize | undefined, next: LabelSize | undefined) {
if (prev) {
toggleState(this.elementInternals, `${prev}`, false);
}
if (next) {
toggleState(this.elementInternals, `${next}`, true);
}
}

/**
* Specifies font weight of a label
*
Expand All @@ -26,6 +48,20 @@ export class Label extends FASTElement {
@attr
public weight?: LabelWeight;

/**
* Handles changes to weight attribute custom states
* @param prev - the previous state
* @param next - the next state
*/
public weightChanged(prev: LabelWeight | undefined, next: LabelWeight | undefined) {
if (prev) {
toggleState(this.elementInternals, `${prev}`, false);
}
if (next) {
toggleState(this.elementInternals, `${next}`, true);
}
}

/**
* Specifies styles for label when associated input is disabled
*
Expand All @@ -36,6 +72,15 @@ export class Label extends FASTElement {
@attr({ mode: 'boolean' })
public disabled: boolean = false;

/**
* Handles changes to disabled attribute custom states
* @param prev - the previous state
* @param next - the next state
*/
public disabledChanged(prev: boolean | undefined, next: boolean | undefined) {
toggleState(this.elementInternals, 'disabled', next);
}

/**
* Specifies styles for label when associated input is a required field
*
Expand Down

0 comments on commit a3b7e64

Please sign in to comment.