Skip to content

Commit

Permalink
Adds Label as a new web component (#27344)
Browse files Browse the repository at this point in the history
* label init

* merge web-components-v3

* updates readme

* updates styles

* adds label styles

* removes dead import

* divides accordion and accordion item readme

* updates label docs

* updates label template

* updates label template and styles

* updates label stories

* label updates per review

* removes attr decorator on label

* label: yarn change

* radio: updates attribute story names

* label: removes attribute binding on template

* label: removes template tag

* label: updates per review

* label: adds export to root json

* adds disabled attr

* removes dead code

* Update change/@fluentui-web-components-2b95d12e-1c42-47e8-88da-aa67dac5673b.json

Co-authored-by: Chris Holt <chhol@microsoft.com>

* label: removes dead import, updates storybook args to use consts

* label: removes dead imports

* label: updates asterisk styling

* label:docs: enumerates rendered element API difs between FUIRv9 and FUIWC3

* label: updates docs

* label: fixes mispelling in docs

* label: updates template, docs

* label: updates docs

* label: replaces arg values with label options const

---------

Co-authored-by: Chris Holt <chhol@microsoft.com>
  • Loading branch information
2 people authored and radium-v committed Apr 30, 2024
1 parent 9f91f2a commit 4d37d0f
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat(label): adds Label as a new web component",
"packageName": "@fluentui/web-components",
"email": "brianbrady@microsoft.com",
"dependentChangeType": "patch"
}
4 changes: 4 additions & 0 deletions packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
"types": "./dist/esm/image/define.d.ts",
"default": "./dist/esm/image/define.js"
},
"./label": {
"types": "./dist/esm/label/define.d.ts",
"default": "./dist/esm/label/define.js"
},
"./menu-button": {
"types": "./dist/esm/menu-button/define.d.ts",
"default": "./dist/esm/menu-button/define.js"
Expand Down
1 change: 1 addition & 0 deletions packages/web-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './button/index.js';
export * from './counter-badge/index.js';
export * from './divider/index.js';
export * from './image/index.js';
export * from './label/index.js';
export * from './menu-button/index.js';
export * from './progress-bar/index.js';
export * from './slider/index.js';
Expand Down
123 changes: 123 additions & 0 deletions packages/web-components/src/label/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Label

> A label represents a caption for an item in a user interface.
<br />

## **Design Spec**

[Link to Label Design Spec in Figma](https://www.figma.com/file/jpWO2FMBefirTyThf5Rg2P/Label?node-id=2%3A476&t=QCdofuTbXkUjMS4d-0)

<br />

## **Engineering Spec**

<br />

The fluent-label has several visual font size (small, medium, large) and font weight(regular, semibold) options. The fluent-label also provides appearances for required and disabled states.

<br />

_Note about form association_

In web components, when using the shadow DOM, it's not feasible to associate elements across the shadow DOM boundary using the traditional `for` attribute, since the shadow DOM creates a boundary that prevents the label element from accessing the input element's id attribute. Instead, the WC3 Label component uses the `aria-labelledby` attribute to associate the label element with the input element. This attribute has a value that matches the id of another element on the page, which serves as a label for the input element.

<br />

### Use Case

Creating a simple label element with an optional info icon and optional required state

<br />

## Class: `Label`

<br />

### **Variables**

<br />

### **Fields**

| Name | Privacy | Type | Default | Description |
| ---------- | ------- | ------------------------------ | ----------- | ------------------------------------ |
| `required` | public | `boolean` | `false` | Specifies required styling for label |
| `disabled` | public | `boolean` | `false` | Sets disabled state for label |
| `size` | public | `"small"` `"medium"` `"large"` | `"medium"` | Specifies font size for label |
| `weight` | public | `"regular"` `"semibold"` | `"regular"` | Specifies font weight for label |

<br />

### **Methods**

<br />

### **Events**

<br />

### **Attributes**

| Name | Field |
| ---------- | -------- |
| `required` | required |
| `disabled` | disabled |
| `size` | size |
| `weight` | weight |

<br />

### **Slots**

| Name | Description |
| ---- | -------------------------------------- |
| | Default slotted content for label text |

<br />

### **Template**

```html
<slot></slot> <span part="asterisk" class="asterisk" ?hidden="${x => !x.required}">*</span>
```

## **Accessibility**

[W3 Label Spec](https://www.w3.org/WAI/tutorials/forms/labels/)

<br />

### **WAI-ARIA Roles, States, and Properties**

<br />

- [`aria-labelledby`](https://www.w3.org/TR/wai-aria-1.2/#aria-labelledby)

<br />
<hr />
<br />

## **Preparation**

<br />

### **Fluent Web Component v3 v.s Fluent React 9**

<br />

**Component and Slot Mapping**

| Fluent UI React 9 | Fluent Web Components 3 |
| ----------------- | ----------------------- |
| `<Label>` | `<fluent-label>` |

<br />

**Additional Deltas**
| | Fluent UI React 9 | Fluent Web Components 3 |
|---------------------| --------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------- |
| Renders | `HTMLLabelElement` | `HTMLElement` |
| API | [`HTMLLabelElement` Spec](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement) | [`HTMLElement` Spec](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) |
| Shadow DOM | n/a | uses Shadow DOM |
| Accessibility | Uses `for` attribute to associate with form elements | Uses `aria-labelledby` to associate with form elements, does not have a `for` attribute |
4 changes: 4 additions & 0 deletions packages/web-components/src/label/define.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition } from './label.definition.js';

definition.define(FluentDesignSystem.registry);
4 changes: 4 additions & 0 deletions packages/web-components/src/label/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './label.js';
export { definition as LabelDefinition } from './label.definition.js';
export { styles as LabelStyles } from './label.styles.js';
export { template as LabelTemplate } from './label.template.js';
18 changes: 18 additions & 0 deletions packages/web-components/src/label/label.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { Label } from './label.js';
import { styles } from './label.styles.js';
import { template } from './label.template.js';

/**
* The Fluent Label Element.
*
*
* @public
* @remarks
* HTML Element: \<fluent-label\>
*/
export const definition = Label.compose({
name: `${FluentDesignSystem.prefix}-label`,
template,
styles,
});
30 changes: 30 additions & 0 deletions packages/web-components/src/label/label.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ValuesOf } from '@microsoft/fast-foundation';

/**
* A Labels font size can be small, medium, or large
*/
export const LabelSize = {
small: 'small',
medium: 'medium',
large: 'large',
} as const;

/**
* Applies font size to label
* @public
*/
export type LabelSize = ValuesOf<typeof LabelSize>;

/**
* A label can have a font weight of regular or strong
*/
export const LabelWeight = {
regular: 'regular',
semibold: 'semibold',
} as const;

/**
* Applies font weight to label
* @public
*/
export type LabelWeight = ValuesOf<typeof LabelWeight>;
93 changes: 93 additions & 0 deletions packages/web-components/src/label/label.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { html } from '@microsoft/fast-element';
import type { Args, Meta } from '@storybook/html';
import { renderComponent } from '../helpers.stories.js';
import type { Label as FluentLabel } from './label.js';
import './define.js';
import { LabelSize, LabelWeight } from './label.options.js';

type LabelStoryArgs = Args & FluentLabel;
type LabelStoryMeta = Meta<LabelStoryArgs>;

const storyTemplate = html<LabelStoryArgs>`
<fluent-label
weight="${x => x.weight}"
size="${x => x.size}"
?required="${x => x.required}"
?disabled="${x => x.disabled}"
>Label</fluent-label
>
`;

export default {
title: 'Components/Label',
args: {
required: false,
size: LabelSize.medium,
weight: LabelWeight.regular,
},
argTypes: {
required: {
description: 'Sets required field styling',
table: {
defaultValue: { summary: false },
},
control: {
type: 'boolean',
},
defaultValue: false,
},
disabled: {
description: 'Sets disabled styling',
table: {
defaultValue: { summary: false },
},
control: {
type: 'boolean',
},
defaultValue: false,
},
size: {
description: 'Sets label font size',
table: {
defaultValue: { summary: LabelSize.medium },
},
control: {
type: 'select',
options: Object.values(LabelSize),
},
defaultValue: LabelSize.medium,
},
weight: {
description: 'Sets label font weight',
table: {
defaultValue: { summary: LabelWeight.regular },
},
control: {
type: 'select',
options: Object.values(LabelWeight),
},
defaultValue: LabelWeight.regular,
},
},
} as LabelStoryMeta;

export const Label = renderComponent(storyTemplate).bind({});

export const Size = renderComponent(html<LabelStoryArgs>`
<div style="display: flex; flex-direction: row; justify-content: space-around; align-items: center; gap: 10px;">
<fluent-label size="small">Small Label</fluent-label>
<fluent-label size="medium">Medium Label</fluent-label>
<fluent-label size="large">Large Label</fluent-label>
</div>
`);

export const Weight = renderComponent(html<LabelStoryArgs>`
<div style="display: flex; flex-direction: row; justify-content: space-around; align-items: center; gap: 10px;">
<fluent-label weight="regular">Regular Label</fluent-label>
<fluent-label weight="semibold">Semibold Label</fluent-label>
</div>
`);

export const Required = renderComponent(html<LabelStoryArgs>` <fluent-label required>Required Label</fluent-label> `);

export const Disabled = renderComponent(html<LabelStoryArgs>` <fluent-label disabled>Disabled Label</fluent-label> `);
52 changes: 52 additions & 0 deletions packages/web-components/src/label/label.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { css } from '@microsoft/fast-element';
import { display } from '@microsoft/fast-foundation';
import {
colorNeutralForeground1,
colorNeutralForegroundDisabled,
colorPaletteRedForeground1,
fontFamilyBase,
fontSizeBase200,
fontSizeBase300,
fontSizeBase400,
fontWeightRegular,
fontWeightSemibold,
lineHeightBase200,
lineHeightBase300,
lineHeightBase400,
spacingHorizontalXS,
} from '../theme/design-tokens.js';

/** Label styles
* @public
*/
export const styles = css`
${display('flex')}
:host {
font-family: ${fontFamilyBase};
font-size: ${fontSizeBase300};
line-height: ${lineHeightBase300};
font-weight: ${fontWeightRegular};
color: ${colorNeutralForeground1};
}
.asterisk {
color: ${colorPaletteRedForeground1};
margin-left: ${spacingHorizontalXS};
}
:host([size='small']) {
font-size: ${fontSizeBase200};
line-height: ${lineHeightBase200};
}
:host([size='large']) {
font-size: ${fontSizeBase400};
line-height: ${lineHeightBase400};
font-weight: ${fontWeightSemibold};
}
:host([weight='semibold']) {
font-weight: ${fontWeightSemibold};
}
:host([disabled]),
:host([disabled]) .asterisk {
color: ${colorNeutralForegroundDisabled};
}
`;
15 changes: 15 additions & 0 deletions packages/web-components/src/label/label.template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ElementViewTemplate, html } from '@microsoft/fast-element';
import { Label } from './label.js';

/**
* The template for the Fluent label web-component.
* @public
*/
export function labelTemplate<T extends Label>(): ElementViewTemplate<T> {
return html<T>`
<slot></slot>
<span part="asterisk" class="asterisk" ?hidden="${x => !x.required}">*</span>
`;
}

export const template: ElementViewTemplate<Label> = labelTemplate();
Loading

0 comments on commit 4d37d0f

Please sign in to comment.