diff --git a/packages/web-components/fast-components-msft/docs/api-report.md b/packages/web-components/fast-components-msft/docs/api-report.md index 8aacd40efb6..8adb330643b 100644 --- a/packages/web-components/fast-components-msft/docs/api-report.md +++ b/packages/web-components/fast-components-msft/docs/api-report.md @@ -4,6 +4,7 @@ ```ts +import { Accordion } from '@microsoft/fast-foundation'; import { Anchor } from '@microsoft/fast-foundation'; import { Badge } from '@microsoft/fast-foundation'; import { BaseProgress } from '@microsoft/fast-foundation'; @@ -30,6 +31,10 @@ import { TextField } from '@microsoft/fast-foundation'; // @public (undocumented) export type BadgeAppearance = "accent" | "lightweight" | "neutral" | string; +// @public (undocumented) +export class FASTAccordion extends Accordion { +} + // @public (undocumented) export class FASTAnchor extends Anchor { } diff --git a/packages/web-components/fast-components-msft/src/accordion/accordion-item/accordion-item.styles.ts b/packages/web-components/fast-components-msft/src/accordion/accordion-item/accordion-item.styles.ts new file mode 100644 index 00000000000..4ace01f827f --- /dev/null +++ b/packages/web-components/fast-components-msft/src/accordion/accordion-item/accordion-item.styles.ts @@ -0,0 +1,137 @@ +import { css } from "@microsoft/fast-element"; +import { + display, + focusVisible, + forcedColorsStylesheetBehavior, +} from "@microsoft/fast-foundation"; +import { + neutralDividerRestBehavior, + neutralFocusBehavior, + neutralForegroundActiveBehavior, + neutralForegroundFocusBehavior, + neutralForegroundRestBehavior, +} from "../../styles/"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { heightNumber } from "../../styles/size"; + +export const AccordionItemStyles = css` + ${display("flex")} :host { + box-sizing: border-box; + font-family: var(--body-font); + flex-direction: column; + font-size: var(--type-ramp-minus-1-font-size); + line-height: var(--type-ramp-minus-1-line-height); + border-bottom: calc(var(--outline-width) * 1px) solid var(--neutral-divider-rest); + } + + .region { + display: none; + padding: calc((6 + (var(--design-unit) * 2 * var(--density))) * 1px); + } + + .heading { + display: grid; + position: relative; + grid-template-columns: auto 1fr auto calc(${heightNumber} * 1px); + z-index: 2; + } + + .button { + appearance: none; + border: none; + background: none; + grid-column: 2; + grid-row: 1; + outline: none; + padding: 0 calc((6 + (var(--design-unit) * 2 * var(--density))) * 1px); + text-align: left; + height: calc(${heightNumber} * 1px); + color: var(--neutral-foreground-rest); + cursor: pointer; + } + + .button:hover { + color: var(--neutral-foreground-hover); + } + + .button:active { + color: var(--neutral-foreground-active); + } + + .button::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + cursor: pointer; + } + + .button:${focusVisible}::before { + outline: none; + border: calc(var(--outline-width) * 1px) solid var(--neutral-focus); + box-shadow: 0 0 0 calc((var(--focus-outline-width) - var(--outline-width)) * 1px) + var(--neutral-focus); + } + + :host(.expanded) .region { + display: flex; + } + + .icon { + display: flex; + align-items: center; + justify-content: center; + grid-column: 4; + z-index: 2; + pointer-events: none; + } + + slot[name="collapsed-icon"] { + display: flex; + } + + :host(.expanded) slot[name="collapsed-icon"] { + display: none; + } + + slot[name="expanded-icon"] { + display: none; + } + + :host(.expanded) slot[name="expanded-icon"] { + display: flex; + } + + .start { + display: flex; + align-items: center; + justify-content: center; + grid-column: 1; + z-index: 2; + } + + .end { + display: flex; + align-items: center; + justify-content: center; + grid-column: 3; + z-index: 2; + } +`.withBehaviors( + neutralDividerRestBehavior, + neutralForegroundActiveBehavior, + neutralForegroundFocusBehavior, + neutralForegroundRestBehavior, + neutralFocusBehavior, + forcedColorsStylesheetBehavior( + css` + .button:${focusVisible}::before { + border-color: ${SystemColors.Highlight}; + box-shadow: 0 0 0 calc((var(--focus-outline-width) - var(--outline-width)) * 1px) ${SystemColors.Highlight}; + } + ` + ) +); diff --git a/packages/web-components/fast-components-msft/src/accordion/accordion-item/index.ts b/packages/web-components/fast-components-msft/src/accordion/accordion-item/index.ts new file mode 100644 index 00000000000..050d06f0543 --- /dev/null +++ b/packages/web-components/fast-components-msft/src/accordion/accordion-item/index.ts @@ -0,0 +1,13 @@ +import { customElement } from "@microsoft/fast-element"; +import { + AccordionItem, + AccordionItemTemplate as template, +} from "@microsoft/fast-foundation"; +import { AccordionItemStyles as styles } from "./accordion-item.styles"; + +@customElement({ + name: "fast-accordion-item", + template, + styles, +}) +export class FASTAccordionItem extends AccordionItem {} diff --git a/packages/web-components/fast-components-msft/src/accordion/accordion.stories.ts b/packages/web-components/fast-components-msft/src/accordion/accordion.stories.ts new file mode 100644 index 00000000000..6b8a69509b1 --- /dev/null +++ b/packages/web-components/fast-components-msft/src/accordion/accordion.stories.ts @@ -0,0 +1,15 @@ +import { FASTDesignSystemProvider } from "../design-system-provider"; +import Examples from "./fixtures/base.html"; +import { FASTAccordionItem } from "./accordion-item"; +import { FASTAccordion } from "."; + +// Prevent tree-shaking +FASTAccordion; +FASTAccordionItem; +FASTDesignSystemProvider; + +export default { + title: "Accordion", +}; + +export const Base = () => Examples; diff --git a/packages/web-components/fast-components-msft/src/accordion/accordion.styles.ts b/packages/web-components/fast-components-msft/src/accordion/accordion.styles.ts new file mode 100644 index 00000000000..9b4b1748ad5 --- /dev/null +++ b/packages/web-components/fast-components-msft/src/accordion/accordion.styles.ts @@ -0,0 +1,23 @@ +import { css } from "@microsoft/fast-element"; +import { display } from "@microsoft/fast-foundation"; +import { + accentFillRestBehavior, + neutralDividerRestBehavior, + neutralForegroundRestBehavior, +} from "../styles/"; + +export const AccordionStyles = css` + ${display("flex")} :host { + box-sizing: border-box; + flex-direction: column; + font-family: var(--body-font); + font-size: var(--type-ramp-minus-1-font-size); + line-height: var(--type-ramp-minus-1-line-height); + color: var(--neutral-foreground-rest); + border-top: calc(var(--outline-width) * 1px) solid var(--neutral-divider-rest); + } +`.withBehaviors( + accentFillRestBehavior, + neutralDividerRestBehavior, + neutralForegroundRestBehavior +); diff --git a/packages/web-components/fast-components-msft/src/accordion/fixtures/base.html b/packages/web-components/fast-components-msft/src/accordion/fixtures/base.html new file mode 100644 index 00000000000..3b85ca61cad --- /dev/null +++ b/packages/web-components/fast-components-msft/src/accordion/fixtures/base.html @@ -0,0 +1,168 @@ + + +

Accordion

+

Default

+ + +
+ +
+
+ +
+ Panel one + + + + + + + + + + Panel one content +
+ + Panel two + + + + + + + + + + Panel two content + + + Panel three + + + + + + + + + + Panel three content + +
+

Single expand

+ + +
+ +
+
+ +
+ Panel one + + + + + + + + + + Panel one content +
+ + Panel Two + + + + + + + + + + Panel two content + + + Panel three + + + + + + + + + + Panel three content + +
+

With disabled item

+ + +
+ +
+
+ +
+ Panel two + + + + + + + + + + Panel one content +
+ +
+ +
+
+ +
+ Disabled + + + + + + + + + + Disabled content +
+ +
+ +
+
+ +
+ Panel three + + + + + + + + + + Panel three content +
+
+
\ No newline at end of file diff --git a/packages/web-components/fast-components-msft/src/accordion/index.ts b/packages/web-components/fast-components-msft/src/accordion/index.ts new file mode 100644 index 00000000000..46d4832f179 --- /dev/null +++ b/packages/web-components/fast-components-msft/src/accordion/index.ts @@ -0,0 +1,10 @@ +import { customElement } from "@microsoft/fast-element"; +import { Accordion, AccordionTemplate as template } from "@microsoft/fast-foundation"; +import { AccordionStyles as styles } from "./accordion.styles"; + +@customElement({ + name: "fast-accordion", + template, + styles, +}) +export class FASTAccordion extends Accordion {} diff --git a/packages/web-components/fast-components-msft/src/index.ts b/packages/web-components/fast-components-msft/src/index.ts index f72dde059bc..63967623995 100644 --- a/packages/web-components/fast-components-msft/src/index.ts +++ b/packages/web-components/fast-components-msft/src/index.ts @@ -1,3 +1,4 @@ +export * from "./accordion/"; export * from "./anchor/"; export * from "./badge/"; export * from "./button/"; diff --git a/packages/web-components/fast-components-msft/temp/api-report.md b/packages/web-components/fast-components-msft/temp/api-report.md index 8aacd40efb6..8adb330643b 100644 --- a/packages/web-components/fast-components-msft/temp/api-report.md +++ b/packages/web-components/fast-components-msft/temp/api-report.md @@ -4,6 +4,7 @@ ```ts +import { Accordion } from '@microsoft/fast-foundation'; import { Anchor } from '@microsoft/fast-foundation'; import { Badge } from '@microsoft/fast-foundation'; import { BaseProgress } from '@microsoft/fast-foundation'; @@ -30,6 +31,10 @@ import { TextField } from '@microsoft/fast-foundation'; // @public (undocumented) export type BadgeAppearance = "accent" | "lightweight" | "neutral" | string; +// @public (undocumented) +export class FASTAccordion extends Accordion { +} + // @public (undocumented) export class FASTAnchor extends Anchor { } diff --git a/packages/web-components/fast-components/docs/api-report.md b/packages/web-components/fast-components/docs/api-report.md index 26e7d22b33b..9cc4d730a2a 100644 --- a/packages/web-components/fast-components/docs/api-report.md +++ b/packages/web-components/fast-components/docs/api-report.md @@ -4,6 +4,7 @@ ```ts +import { Accordion } from '@microsoft/fast-foundation'; import { Anchor } from '@microsoft/fast-foundation'; import { Badge } from '@microsoft/fast-foundation'; import { BaseProgress } from '@microsoft/fast-foundation'; @@ -152,6 +153,10 @@ export const accentForegroundRestBehavior: import("@microsoft/fast-foundation"). // @public (undocumented) export function createColorPalette(baseColor: any): string[]; +// @public (undocumented) +export class FASTAccordion extends Accordion { +} + // @public (undocumented) export class FASTAnchor extends Anchor { } diff --git a/packages/web-components/fast-components/src/accordion/README.md b/packages/web-components/fast-components/src/accordion/README.md new file mode 100644 index 00000000000..287588e0d1b --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/README.md @@ -0,0 +1,4 @@ +# fast-accordion +`fast-accordion` is a web component implementation of an [Accordion](https://w3c.github.io/aria-practices/#accordion). + +For more information view the [component specification](../../../fast-foundation/src/accordion/accordion.spec.md). \ No newline at end of file diff --git a/packages/web-components/fast-components/src/accordion/accordion-item/accordion-item.styles.ts b/packages/web-components/fast-components/src/accordion/accordion-item/accordion-item.styles.ts new file mode 100644 index 00000000000..371c03ea72b --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/accordion-item/accordion-item.styles.ts @@ -0,0 +1,137 @@ +import { css } from "@microsoft/fast-element"; +import { + display, + focusVisible, + forcedColorsStylesheetBehavior, +} from "@microsoft/fast-foundation"; +import { + neutralDividerRestBehavior, + neutralFocusBehavior, + neutralForegroundActiveBehavior, + neutralForegroundFocusBehavior, + neutralForegroundRestBehavior, +} from "../../styles/recipes"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { heightNumber } from "../../styles/size"; + +export const AccordionItemStyles = css` + ${display("flex")} :host { + box-sizing: border-box; + font-family: var(--body-font); + flex-direction: column; + font-size: var(--type-ramp-minus-1-font-size); + line-height: var(--type-ramp-minus-1-line-height); + border-bottom: calc(var(--outline-width) * 1px) solid var(--neutral-divider-rest); + } + + .region { + display: none; + padding: calc((6 + (var(--design-unit) * 2 * var(--density))) * 1px); + } + + .heading { + display: grid; + position: relative; + grid-template-columns: auto 1fr auto calc(${heightNumber} * 1px); + z-index: 2; + } + + .button { + appearance: none; + border: none; + background: none; + grid-column: 2; + grid-row: 1; + outline: none; + padding: 0 calc((6 + (var(--design-unit) * 2 * var(--density))) * 1px); + text-align: left; + height: calc(${heightNumber} * 1px); + color: var(--neutral-foreground-rest); + cursor: pointer; + } + + .button:hover { + color: var(--neutral-foreground-hover); + } + + .button:active { + color: var(--neutral-foreground-active); + } + + .button::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + cursor: pointer; + } + + .button:${focusVisible}::before { + outline: none; + border: calc(var(--outline-width) * 1px) solid var(--neutral-focus); + box-shadow: 0 0 0 calc((var(--focus-outline-width) - var(--outline-width)) * 1px) + var(--neutral-focus); + } + + :host(.expanded) .region { + display: flex; + } + + .icon { + display: flex; + align-items: center; + justify-content: center; + grid-column: 4; + z-index: 2; + pointer-events: none; + } + + slot[name="collapsed-icon"] { + display: flex; + } + + :host(.expanded) slot[name="collapsed-icon"] { + display: none; + } + + slot[name="expanded-icon"] { + display: none; + } + + :host(.expanded) slot[name="expanded-icon"] { + display: flex; + } + + .start { + display: flex; + align-items: center; + justify-content: center; + grid-column: 1; + z-index: 2; + } + + .end { + display: flex; + align-items: center; + justify-content: center; + grid-column: 3; + z-index: 2; + } +`.withBehaviors( + neutralDividerRestBehavior, + neutralForegroundActiveBehavior, + neutralForegroundFocusBehavior, + neutralForegroundRestBehavior, + neutralFocusBehavior, + forcedColorsStylesheetBehavior( + css` + .button:${focusVisible}::before { + border-color: ${SystemColors.Highlight}; + box-shadow: 0 0 0 calc((var(--focus-outline-width) - var(--outline-width)) * 1px) ${SystemColors.Highlight}; + } + ` + ) +); diff --git a/packages/web-components/fast-components/src/accordion/accordion-item/index.ts b/packages/web-components/fast-components/src/accordion/accordion-item/index.ts new file mode 100644 index 00000000000..050d06f0543 --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/accordion-item/index.ts @@ -0,0 +1,13 @@ +import { customElement } from "@microsoft/fast-element"; +import { + AccordionItem, + AccordionItemTemplate as template, +} from "@microsoft/fast-foundation"; +import { AccordionItemStyles as styles } from "./accordion-item.styles"; + +@customElement({ + name: "fast-accordion-item", + template, + styles, +}) +export class FASTAccordionItem extends AccordionItem {} diff --git a/packages/web-components/fast-components/src/accordion/accordion.stories.ts b/packages/web-components/fast-components/src/accordion/accordion.stories.ts new file mode 100644 index 00000000000..6b8a69509b1 --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/accordion.stories.ts @@ -0,0 +1,15 @@ +import { FASTDesignSystemProvider } from "../design-system-provider"; +import Examples from "./fixtures/base.html"; +import { FASTAccordionItem } from "./accordion-item"; +import { FASTAccordion } from "."; + +// Prevent tree-shaking +FASTAccordion; +FASTAccordionItem; +FASTDesignSystemProvider; + +export default { + title: "Accordion", +}; + +export const Base = () => Examples; diff --git a/packages/web-components/fast-components/src/accordion/accordion.styles.ts b/packages/web-components/fast-components/src/accordion/accordion.styles.ts new file mode 100644 index 00000000000..2b03070c2e1 --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/accordion.styles.ts @@ -0,0 +1,23 @@ +import { css } from "@microsoft/fast-element"; +import { display } from "@microsoft/fast-foundation"; +import { + accentFillRestBehavior, + neutralDividerRestBehavior, + neutralForegroundRestBehavior, +} from "../styles/recipes"; + +export const AccordionStyles = css` + ${display("flex")} :host { + box-sizing: border-box; + flex-direction: column; + font-family: var(--body-font); + font-size: var(--type-ramp-minus-1-font-size); + line-height: var(--type-ramp-minus-1-line-height); + color: var(--neutral-foreground-rest); + border-top: calc(var(--outline-width) * 1px) solid var(--neutral-divider-rest); + } +`.withBehaviors( + accentFillRestBehavior, + neutralDividerRestBehavior, + neutralForegroundRestBehavior +); diff --git a/packages/web-components/fast-components/src/accordion/fixtures/base.html b/packages/web-components/fast-components/src/accordion/fixtures/base.html new file mode 100644 index 00000000000..3b85ca61cad --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/fixtures/base.html @@ -0,0 +1,168 @@ + + +

Accordion

+

Default

+ + +
+ +
+
+ +
+ Panel one + + + + + + + + + + Panel one content +
+ + Panel two + + + + + + + + + + Panel two content + + + Panel three + + + + + + + + + + Panel three content + +
+

Single expand

+ + +
+ +
+
+ +
+ Panel one + + + + + + + + + + Panel one content +
+ + Panel Two + + + + + + + + + + Panel two content + + + Panel three + + + + + + + + + + Panel three content + +
+

With disabled item

+ + +
+ +
+
+ +
+ Panel two + + + + + + + + + + Panel one content +
+ +
+ +
+
+ +
+ Disabled + + + + + + + + + + Disabled content +
+ +
+ +
+
+ +
+ Panel three + + + + + + + + + + Panel three content +
+
+
\ No newline at end of file diff --git a/packages/web-components/fast-components/src/accordion/index.ts b/packages/web-components/fast-components/src/accordion/index.ts new file mode 100644 index 00000000000..46d4832f179 --- /dev/null +++ b/packages/web-components/fast-components/src/accordion/index.ts @@ -0,0 +1,10 @@ +import { customElement } from "@microsoft/fast-element"; +import { Accordion, AccordionTemplate as template } from "@microsoft/fast-foundation"; +import { AccordionStyles as styles } from "./accordion.styles"; + +@customElement({ + name: "fast-accordion", + template, + styles, +}) +export class FASTAccordion extends Accordion {} diff --git a/packages/web-components/fast-components/src/index.ts b/packages/web-components/fast-components/src/index.ts index fe7b86120e7..71e390d2b9b 100644 --- a/packages/web-components/fast-components/src/index.ts +++ b/packages/web-components/fast-components/src/index.ts @@ -1,3 +1,4 @@ +export * from "./accordion/index"; export * from "./anchor/index"; export * from "./badge/index"; export * from "./button/index"; diff --git a/packages/web-components/fast-components/temp/api-report.md b/packages/web-components/fast-components/temp/api-report.md index 26e7d22b33b..9cc4d730a2a 100644 --- a/packages/web-components/fast-components/temp/api-report.md +++ b/packages/web-components/fast-components/temp/api-report.md @@ -4,6 +4,7 @@ ```ts +import { Accordion } from '@microsoft/fast-foundation'; import { Anchor } from '@microsoft/fast-foundation'; import { Badge } from '@microsoft/fast-foundation'; import { BaseProgress } from '@microsoft/fast-foundation'; @@ -152,6 +153,10 @@ export const accentForegroundRestBehavior: import("@microsoft/fast-foundation"). // @public (undocumented) export function createColorPalette(baseColor: any): string[]; +// @public (undocumented) +export class FASTAccordion extends Accordion { +} + // @public (undocumented) export class FASTAnchor extends Anchor { } diff --git a/packages/web-components/fast-foundation/docs/api-report.md b/packages/web-components/fast-foundation/docs/api-report.md index 6c5e41430e1..730d45d76fb 100644 --- a/packages/web-components/fast-foundation/docs/api-report.md +++ b/packages/web-components/fast-foundation/docs/api-report.md @@ -12,6 +12,48 @@ import { FASTElement } from '@microsoft/fast-element'; import { Orientation } from '@microsoft/fast-web-utilities'; import { PartialFASTElementDefinition } from '@microsoft/fast-element'; +// @public (undocumented) +export class Accordion extends FASTElement { + // (undocumented) + accordionItems: HTMLElement[]; + // (undocumented) + accordionItemsChanged(oldValue: any, newValue: any): void; + // (undocumented) + expandmode: AccordionExpandMode; + } + +// @public (undocumented) +export enum AccordionExpandMode { + // (undocumented) + multi = "multi", + // (undocumented) + single = "single" +} + +// @public (undocumented) +export class AccordionItem extends FASTElement { + // (undocumented) + clickHandler: (e: MouseEvent) => void; + // (undocumented) + expandbutton: HTMLElement; + // (undocumented) + expanded: boolean; + // (undocumented) + headinglevel: 1 | 2 | 3 | 4 | 5 | 6; + // (undocumented) + id: string; +} + +// @public (undocumented) +export interface AccordionItem extends StartEnd { +} + +// @public (undocumented) +export const AccordionItemTemplate: import("@microsoft/fast-element").ViewTemplate; + +// @public (undocumented) +export const AccordionTemplate: import("@microsoft/fast-element").ViewTemplate; + // @public (undocumented) export class Anchor extends FASTElement { // (undocumented) diff --git a/packages/web-components/fast-foundation/src/accordion/README.md b/packages/web-components/fast-foundation/src/accordion/README.md new file mode 100644 index 00000000000..1b86cc684c6 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/README.md @@ -0,0 +1,24 @@ +--- +id: fast-accordion +title: fast-accordion +sidebar_label: fast-accordion +custom_edit_url: https://github.com/microsoft/fast-dna/edit/master/packages/web-components/fast-foundation/src/accordion/README.md +--- + +## Applying Custom Styles + +```ts +import { customElement } from "@microsoft/fast-element"; +import { Accordion, AccordionTemplate as template } from "@microsoft/fast-foundation"; +import { MyAccordionStyles as styles } from "./accordion.styles"; + +@customElement({ + name: "fast-accordion", + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, +}) +export class FASTAccordion extends Accordion {} +``` \ No newline at end of file diff --git a/packages/web-components/fast-foundation/src/accordion/accordion-item/README.md b/packages/web-components/fast-foundation/src/accordion/accordion-item/README.md new file mode 100644 index 00000000000..e858a9d6071 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/accordion-item/README.md @@ -0,0 +1,24 @@ +--- +id: fast-accordion-item +title: fast-accordion-item +sidebar_label: fast-accordion-item +custom_edit_url: https://github.com/microsoft/fast-dna/edit/master/packages/web-components/fast-foundation/src/accordion/README.md +--- + +## Applying Custom Styles + +```ts +import { customElement } from "@microsoft/fast-element"; +import { AccordionItem, AccordionItemTemplate as template } from "@microsoft/fast-foundation"; +import { MyAccordionItemStyles as styles } from "./accordion-item.styles"; + +@customElement({ + name: "fast-accordion-item", + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, +}) +export class FASTAccordionItem extends AccordionItem {} +``` \ No newline at end of file diff --git a/packages/web-components/fast-foundation/src/accordion/accordion-item/accordion-item.template.ts b/packages/web-components/fast-foundation/src/accordion/accordion-item/accordion-item.template.ts new file mode 100644 index 00000000000..6bb2974a9f4 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/accordion-item/accordion-item.template.ts @@ -0,0 +1,45 @@ +import { html, ref } from "@microsoft/fast-element"; +import { endTemplate, startTemplate } from "../../patterns/start-end"; +import { AccordionItem } from "./accordion-item"; + +export const AccordionItemTemplate = html` + +`; diff --git a/packages/web-components/fast-foundation/src/accordion/accordion-item/accordion-item.ts b/packages/web-components/fast-foundation/src/accordion/accordion-item/accordion-item.ts new file mode 100644 index 00000000000..4682146f098 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/accordion-item/accordion-item.ts @@ -0,0 +1,39 @@ +import { + attr, + FASTElement, + nullableNumberConverter, + observable, +} from "@microsoft/fast-element"; +import { StartEnd } from "../../patterns/start-end"; +import { applyMixins } from "../../utilities/apply-mixins"; + +export class AccordionItem extends FASTElement { + @attr({ + attribute: "heading-level", + mode: "fromView", + converter: nullableNumberConverter, + }) + public headinglevel: 1 | 2 | 3 | 4 | 5 | 6 = 2; + + @attr({ mode: "boolean" }) + public expanded: boolean = false; + + @attr + public id: string; + + public expandbutton: HTMLElement; + + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + public clickHandler = (e: MouseEvent) => { + this.expanded = !this.expanded; + this.change(); + }; + + private change = (): void => { + this.$emit("change"); + }; +} + +/* eslint-disable-next-line */ +export interface AccordionItem extends StartEnd {} +applyMixins(AccordionItem, StartEnd); diff --git a/packages/web-components/fast-foundation/src/accordion/accordion-item/index.ts b/packages/web-components/fast-foundation/src/accordion/accordion-item/index.ts new file mode 100644 index 00000000000..4ed6a77ca78 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/accordion-item/index.ts @@ -0,0 +1,2 @@ +export * from "./accordion-item.template"; +export * from "./accordion-item"; diff --git a/specs/accordion.md b/packages/web-components/fast-foundation/src/accordion/accordion.spec.md similarity index 89% rename from specs/accordion.md rename to packages/web-components/fast-foundation/src/accordion/accordion.spec.md index 5ac05544469..3abdd08a00b 100644 --- a/specs/accordion.md +++ b/packages/web-components/fast-foundation/src/accordion/accordion.spec.md @@ -68,7 +68,8 @@ As defined by the W3C: *Slot Names* - default - heading -- glyph +- collapsed-icon +- expanded-icon - start - end @@ -89,7 +90,10 @@ As defined by the W3C: Panel one - + + + +
Panel two - + + + +
Panel three - + + + +
Panel one - ^ + ^ Panel one content Panel two - ^ + ^ Panel two content Panel three - ^ + ^ Panel three content diff --git a/packages/web-components/fast-foundation/src/accordion/accordion.template.ts b/packages/web-components/fast-foundation/src/accordion/accordion.template.ts new file mode 100644 index 00000000000..5e36bd1f344 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/accordion.template.ts @@ -0,0 +1,8 @@ +import { html, slotted } from "@microsoft/fast-element"; +import { Accordion } from "./accordion"; + +export const AccordionTemplate = html` + +`; diff --git a/packages/web-components/fast-foundation/src/accordion/accordion.ts b/packages/web-components/fast-foundation/src/accordion/accordion.ts new file mode 100644 index 00000000000..92d441d98db --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/accordion.ts @@ -0,0 +1,131 @@ +import { attr, FASTElement, observable } from "@microsoft/fast-element"; +import { + keyCodeArrowDown, + keyCodeArrowUp, + keyCodeEnd, + keyCodeHome, + wrapInBounds, +} from "@microsoft/fast-web-utilities"; +import { AccordionItem } from "./accordion-item"; + +export enum AccordionExpandMode { + single = "single", + multi = "multi", +} + +export class Accordion extends FASTElement { + @attr({ attribute: "expand-mode" }) + public expandmode: AccordionExpandMode = AccordionExpandMode.multi; + + @observable + public accordionItems: HTMLElement[]; + public accordionItemsChanged(oldValue, newValue): void { + if (this.$fastController.isConnected) { + this.removeItemListeners(oldValue); + this.accordionIds = this.getItemIds(); + this.setItems(); + } + } + + private activeid: string; + private activeItemIndex: number = 0; + private accordionIds: Array; + + private change = (): void => { + this.$emit("change"); + }; + + private setItems = (): void => { + this.accordionIds = this.getItemIds(); + this.accordionItems.forEach((item: HTMLElement, index: number) => { + if (item instanceof AccordionItem) { + item.addEventListener("change", this.activeItemChange); + if (this.isSingleExpandMode()) { + this.activeItemIndex !== index + ? (item.expanded = false) + : (item.expanded = true); + } + } + const itemId: string | null = this.accordionIds[index]; + item.setAttribute( + "id", + typeof itemId !== "string" ? `accordion-${index + 1}` : itemId + ); + this.activeid = this.accordionIds[this.activeItemIndex] as string; + item.addEventListener("keydown", this.handleItemKeyDown); + }); + }; + + private resetItems(): void { + this.accordionItems.forEach((item: AccordionItem, index: number) => { + item.expanded = false; + }); + } + + private removeItemListeners = (oldValue: any): void => { + oldValue.forEach((item: HTMLElement, index: number) => { + item.removeEventListener("change", this.activeItemChange); + item.removeEventListener("keydown", this.handleItemKeyDown); + }); + }; + + private activeItemChange = (event): void => { + const selectedItem = event.target as HTMLElement; + if (this.isSingleExpandMode()) { + this.resetItems(); + event.target.expanded = true; + } + this.activeid = event.target.getAttribute("id"); + this.activeItemIndex = Array.from(this.accordionItems).indexOf(selectedItem); + this.change(); + }; + + private getItemIds(): Array { + return this.accordionItems.map((accordionItem: HTMLElement) => { + return accordionItem.getAttribute("id"); + }); + } + + private isSingleExpandMode(): boolean { + return this.expandmode === AccordionExpandMode.single; + } + + private handleItemKeyDown = (event: KeyboardEvent): void => { + const keyCode: number = event.keyCode; + this.accordionIds = this.getItemIds(); + switch (keyCode) { + case keyCodeArrowUp: + event.preventDefault(); + this.adjust(-1); + break; + case keyCodeArrowDown: + event.preventDefault(); + this.adjust(1); + break; + case keyCodeHome: + this.activeItemIndex = 0; + this.focusItem(); + break; + case keyCodeEnd: + this.activeItemIndex = this.accordionItems.length - 1; + this.focusItem(); + break; + } + }; + + private adjust(adjustment: number): void { + this.activeItemIndex = wrapInBounds( + 0, + this.accordionItems.length - 1, + this.activeItemIndex + adjustment + ); + this.focusItem(); + } + + private focusItem(): void { + const element: HTMLElement = this.accordionItems[this.activeItemIndex]; + if (element instanceof AccordionItem) { + element.expandbutton.focus(); + } + } +} diff --git a/packages/web-components/fast-foundation/src/accordion/index.ts b/packages/web-components/fast-foundation/src/accordion/index.ts new file mode 100644 index 00000000000..806fb840849 --- /dev/null +++ b/packages/web-components/fast-foundation/src/accordion/index.ts @@ -0,0 +1,3 @@ +export * from "./accordion.template"; +export * from "./accordion"; +export * from "./accordion-item"; diff --git a/packages/web-components/fast-foundation/src/index.ts b/packages/web-components/fast-foundation/src/index.ts index bd0a945a07e..1c625d62657 100644 --- a/packages/web-components/fast-foundation/src/index.ts +++ b/packages/web-components/fast-foundation/src/index.ts @@ -1,3 +1,4 @@ +export * from "./accordion/index"; export * from "./anchor/index"; export * from "./badge/index"; export * from "./button/index"; diff --git a/packages/web-components/fast-foundation/src/patterns/start-end.ts b/packages/web-components/fast-foundation/src/patterns/start-end.ts index 2ee49d818b6..997b3590a0c 100644 --- a/packages/web-components/fast-foundation/src/patterns/start-end.ts +++ b/packages/web-components/fast-foundation/src/patterns/start-end.ts @@ -18,7 +18,7 @@ export class StartEnd { } export const endTemplate = html` - + ` `; export const startTemplate = html` - +