From 97e753464f623c2b9cd458ecc5f2c55961da9095 Mon Sep 17 00:00:00 2001
From: Brian Heston <47367562+bheston@users.noreply.github.com>
Date: Tue, 18 Oct 2022 13:53:38 -0700
Subject: [PATCH] Adaptive UI Explorer - initial port (#6424)
Initial port of Color Explorer into the new Adaptive UI Explorer component
---
...-ba5b0bf9-7335-4d14-a6ce-140f265d1674.json | 7 +
package.json | 1 +
.../adaptive-ui-explorer/.eslintignore | 10 +
.../adaptive-ui-explorer/.eslintrc.json | 6 +
.../tooling/adaptive-ui-explorer/.gitignore | 3 +
.../tooling/adaptive-ui-explorer/.npmignore | 7 +
packages/tooling/adaptive-ui-explorer/.npmrc | 1 +
.../adaptive-ui-explorer/.prettierignore | 3 +
.../tooling/adaptive-ui-explorer/README.md | 0
.../tooling/adaptive-ui-explorer/package.json | 50 ++
.../tooling/adaptive-ui-explorer/src/app.ts | 349 +++++++++++
.../adaptive-ui-explorer/src/colors.ts | 25 +
.../src/component-type.ts | 6 +
.../src/components/adaptive-component.ts | 117 ++++
.../src/components/color-block.ts | 558 ++++++++++++++++++
.../control-pane/control-pane.styles.ts | 38 ++
.../control-pane/control-pane.template.ts | 79 +++
.../components/control-pane/control-pane.ts | 19 +
.../src/components/control-pane/index.ts | 11 +
.../src/components/index.ts | 8 +
.../src/components/layer-background/index.ts | 131 ++++
.../src/components/palette-gradient/index.ts | 3 +
.../palette-gradient.styles.ts | 38 ++
.../palette-gradient.template.ts | 32 +
.../palette-gradient/palette-gradient.ts | 21 +
.../src/components/sample-app/index.ts | 9 +
.../sample-app/sample-app.styles.ts | 179 ++++++
.../sample-app/sample-app.template.ts | 118 ++++
.../src/components/sample-app/sample-app.ts | 3 +
.../src/components/sample-page/index.ts | 9 +
.../sample-page/sample-page.styles.ts | 137 +++++
.../sample-page/sample-page.template.ts | 122 ++++
.../src/components/sample-page/sample-page.ts | 3 +
.../src/components/swatch.ts | 217 +++++++
.../tooling/adaptive-ui-explorer/src/index.ts | 3 +
.../adaptive-ui-explorer/tsconfig.json | 17 +
.../adaptive-ui-explorer/webpack.config.cjs | 56 ++
yarn.lock | 21 +-
38 files changed, 2416 insertions(+), 1 deletion(-)
create mode 100644 change/@microsoft-adaptive-ui-explorer-ba5b0bf9-7335-4d14-a6ce-140f265d1674.json
create mode 100644 packages/tooling/adaptive-ui-explorer/.eslintignore
create mode 100644 packages/tooling/adaptive-ui-explorer/.eslintrc.json
create mode 100644 packages/tooling/adaptive-ui-explorer/.gitignore
create mode 100644 packages/tooling/adaptive-ui-explorer/.npmignore
create mode 100644 packages/tooling/adaptive-ui-explorer/.npmrc
create mode 100644 packages/tooling/adaptive-ui-explorer/.prettierignore
create mode 100644 packages/tooling/adaptive-ui-explorer/README.md
create mode 100644 packages/tooling/adaptive-ui-explorer/package.json
create mode 100644 packages/tooling/adaptive-ui-explorer/src/app.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/colors.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/component-type.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/adaptive-component.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/color-block.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.styles.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.template.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/control-pane/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/layer-background/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.styles.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-app/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.styles.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.template.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-page/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.styles.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.template.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/components/swatch.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/src/index.ts
create mode 100644 packages/tooling/adaptive-ui-explorer/tsconfig.json
create mode 100644 packages/tooling/adaptive-ui-explorer/webpack.config.cjs
diff --git a/change/@microsoft-adaptive-ui-explorer-ba5b0bf9-7335-4d14-a6ce-140f265d1674.json b/change/@microsoft-adaptive-ui-explorer-ba5b0bf9-7335-4d14-a6ce-140f265d1674.json
new file mode 100644
index 00000000000..ece55901e53
--- /dev/null
+++ b/change/@microsoft-adaptive-ui-explorer-ba5b0bf9-7335-4d14-a6ce-140f265d1674.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Initial port of Color Explorer into the new Adaptive UI Explorer component",
+ "packageName": "@microsoft/adaptive-ui-explorer",
+ "email": "47367562+bheston@users.noreply.github.com",
+ "dependentChangeType": "prerelease"
+}
diff --git a/package.json b/package.json
index 39ed87c13ae..071d06b2692 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"private": true,
"workspaces": {
"packages": [
+ "packages/tooling/adaptive-ui-explorer",
"packages/utilities/*",
"packages/web-components/*",
"sites/fast-color-explorer",
diff --git a/packages/tooling/adaptive-ui-explorer/.eslintignore b/packages/tooling/adaptive-ui-explorer/.eslintignore
new file mode 100644
index 00000000000..6fde7dc2651
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/.eslintignore
@@ -0,0 +1,10 @@
+# don't ever lint node_modules
+node_modules
+# don't lint build output (make sure it's set to your correct build folder name)
+dist
+# don't lint coverage output
+coverage
+# don't lint www
+www
+# don't lint test files
+__test__
diff --git a/packages/tooling/adaptive-ui-explorer/.eslintrc.json b/packages/tooling/adaptive-ui-explorer/.eslintrc.json
new file mode 100644
index 00000000000..42b6e48bec2
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": ["@microsoft/eslint-config-fast-dna", "prettier"],
+ "rules": {
+ "@typescript-eslint/no-non-null-assertion": "off"
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/.gitignore b/packages/tooling/adaptive-ui-explorer/.gitignore
new file mode 100644
index 00000000000..55e944cd3e6
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/.gitignore
@@ -0,0 +1,3 @@
+tsdoc-metadata.json
+temp
+test
diff --git a/packages/tooling/adaptive-ui-explorer/.npmignore b/packages/tooling/adaptive-ui-explorer/.npmignore
new file mode 100644
index 00000000000..9625030161b
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/.npmignore
@@ -0,0 +1,7 @@
+# Tests
+__test__/
+*.spec.*
+*.test.*
+
+# Source files
+src/
diff --git a/packages/tooling/adaptive-ui-explorer/.npmrc b/packages/tooling/adaptive-ui-explorer/.npmrc
new file mode 100644
index 00000000000..43c97e719a5
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/packages/tooling/adaptive-ui-explorer/.prettierignore b/packages/tooling/adaptive-ui-explorer/.prettierignore
new file mode 100644
index 00000000000..60aa645faec
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/.prettierignore
@@ -0,0 +1,3 @@
+dist/*
+test/*
+src/__test__/*
diff --git a/packages/tooling/adaptive-ui-explorer/README.md b/packages/tooling/adaptive-ui-explorer/README.md
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/tooling/adaptive-ui-explorer/package.json b/packages/tooling/adaptive-ui-explorer/package.json
new file mode 100644
index 00000000000..66050c6d757
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "@microsoft/adaptive-ui-explorer",
+ "version": "1.0.0-alpha.1",
+ "description": "A playground for Adaptive UI",
+ "type": "module",
+ "private": true,
+ "main": "dist/esm/index.js",
+ "types": "dist/adaptive-ui-explorer.d.ts",
+ "unpkg": "dist/esm/index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/microsoft/fast.git",
+ "directory": "packages/utilities/adaptive-ui"
+ },
+ "author": {
+ "name": "Microsoft",
+ "url": "https://discord.gg/FcSNfg4"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/microsoft/fast/issues/new/choose"
+ },
+ "homepage": "https://fast.design",
+ "scripts": {
+ "build": "tsc -p ./tsconfig.json",
+ "build:app": "webpack --mode=production",
+ "clean": "node ../../../build/clean.js dist",
+ "prepare": "yarn clean && yarn build",
+ "prettier": "prettier --config ../../../.prettierrc --write \"**/*.ts\"",
+ "prettier:diff": "prettier --config ../../../.prettierrc \"**/*.ts\" --list-different",
+ "start": "webpack-dev-server"
+ },
+ "dependencies": {
+ "@microsoft/adaptive-ui": "^1.0.0-alpha.2",
+ "@microsoft/fast-colors": "^5.3.1",
+ "@microsoft/fast-element": "^2.0.0-beta.6",
+ "@microsoft/fast-foundation": "^3.0.0-alpha.9"
+ },
+ "devDependencies": {
+ "clean-webpack-plugin": "^4.0.0",
+ "html-webpack-plugin": "^5.5.0",
+ "inject-body-webpack-plugin": "^1.3.0",
+ "resolve-typescript-plugin": "^1.2.0",
+ "ts-loader": "^9.3.0",
+ "typescript": "^4.7.3",
+ "webpack": "^5.73.0",
+ "webpack-cli": "^4.9.2",
+ "webpack-dev-server": "^4.9.2"
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/app.ts b/packages/tooling/adaptive-ui-explorer/src/app.ts
new file mode 100644
index 00000000000..348d20c21fa
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/app.ts
@@ -0,0 +1,349 @@
+import {
+ attr,
+ css,
+ customElement,
+ FASTElement,
+ html,
+ observable,
+ ref,
+ repeat,
+ ViewTemplate,
+} from "@microsoft/fast-element";
+import { DesignToken } from "@microsoft/fast-foundation";
+import {
+ accentBaseColor,
+ accentPalette,
+ LayerBaseLuminance,
+ layerFillBaseLuminance,
+ layerFillFixedBase,
+ layerFillFixedMinus1,
+ layerFillFixedMinus2,
+ layerFillFixedMinus3,
+ layerFillFixedMinus4,
+ layerFillFixedPlus1,
+ neutralBaseColor,
+ neutralPalette,
+ Palette,
+ Swatch,
+ SwatchRGB,
+} from "@microsoft/adaptive-ui";
+import { ComponentType } from "./component-type.js";
+import "./components/color-block.js";
+import "./components/control-pane/index.js";
+import "./components/layer-background/index.js";
+import "./components/palette-gradient/palette-gradient.js";
+import "./components/sample-app/sample-app.js";
+
+const sampleTemplate = html`
+
+
+
+
+
+
+
+
+`;
+
+const colorBlockTemplate = html`
+ ${repeat(
+ x => x.backgrounds,
+ html`
+
+ `
+ )}
+`;
+
+const template = html`
+
+
+
+
+
+
+
+
+
+ ${x => x.componentTypeTemplate()}
+
+
+
+
+
+
+
+ x.controlPaneHandler(c.event as CustomEvent)}"
+ >
+
+
+
+
+`;
+
+const styles = css`
+ :host {
+ width: 100%;
+ }
+
+ .container {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .container.fill {
+ width: 100%;
+ height: 100%;
+ }
+
+ .row {
+ position: relative;
+ display: flex;
+ flex-direction: row;
+ flex-basis: auto;
+ }
+
+ .row.fill {
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .canvas {
+ min-width: 300px;
+ flex-grow: 1;
+ }
+
+ .gradient {
+ height: 20px;
+ }
+
+ .control-pane-container {
+ height: 100%;
+ z-index: 1;
+ padding: 40px;
+ position: relative;
+ overflow: auto;
+ width: 320px;
+ box-sizing: border-box;
+ }
+
+ app-color-block {
+ min-width: 400px;
+ }
+`;
+
+export interface SwatchInfo {
+ index: number;
+ color: string;
+ title?: string;
+}
+
+@customElement({
+ name: `app-design-system-provider`,
+ template: html`
+
+ `,
+})
+class DesignSystemProvider extends FASTElement {}
+DesignSystemProvider;
+
+export interface AppAttributes {
+ componentType: ComponentType;
+ neutralColor: string;
+ accentColor: string;
+ showOnlyLayerBackgrounds: boolean;
+}
+
+@customElement({
+ name: "app-main",
+ template,
+ styles,
+})
+export class App extends FASTElement implements AppAttributes {
+ canvas: DesignSystemProvider;
+
+ @attr({ attribute: "component-type" })
+ componentType: ComponentType = ComponentType.backplate;
+
+ @attr({ attribute: "neutral-color" })
+ neutralColor: string;
+ private neutralColorChanged(prev?: string, next?: string) {
+ if (this.$fastController.isConnected && next) {
+ neutralBaseColor.setValueFor(this.canvas, next);
+
+ this.neutralPalette = neutralPalette.getValueFor(this.canvas);
+ this.neutralColors = this.neutralPalette.swatches.map((x: SwatchRGB) =>
+ x.toColorString()
+ );
+
+ this.updateBackgrounds();
+ }
+ }
+
+ @observable
+ neutralPalette: Palette;
+
+ @observable
+ neutralColors: string[] = [];
+
+ @attr({ attribute: "accent-color" })
+ accentColor: string;
+ private accentColorChanged(prev?: string, next?: string) {
+ if (this.$fastController.isConnected && next) {
+ accentBaseColor.setValueFor(this.canvas, next);
+
+ this.accentPalette = accentPalette.getValueFor(this.canvas);
+ }
+ }
+
+ @observable
+ accentPalette: Palette;
+
+ @observable
+ showOnlyLayerBackgrounds: boolean = true;
+ private showOnlyLayerBackgroundsChanged() {
+ if (this.$fastController.isConnected) {
+ this.updateBackgrounds();
+ }
+ }
+
+ @observable
+ backgrounds: SwatchInfo[];
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.neutralColor = "#808080";
+ this.accentColor = "#0078d4";
+ }
+
+ designSystemElement: FASTElement;
+
+ componentTypeTemplate(): ViewTemplate {
+ // if (this.componentType === ComponentType.sample) {
+ // return sampleTemplate;
+ // } else {
+ return colorBlockTemplate;
+ // }
+ }
+
+ private updateBackgrounds(): void {
+ const layers: SwatchInfo[] = this.lightModeLayers.concat(this.darkModeLayers);
+
+ this.backgrounds = this.showOnlyLayerBackgrounds
+ ? layers
+ : this.neutralColors.map(
+ (color: string, index: number): SwatchInfo => {
+ const neutralLayerIndex: number = layers.findIndex(
+ (config: SwatchInfo): boolean => config.color === color
+ );
+
+ return {
+ index,
+ color,
+ title:
+ neutralLayerIndex !== -1
+ ? layers[neutralLayerIndex].title
+ : undefined,
+ };
+ }
+ );
+ }
+
+ private layerTokens: Array<[DesignToken, string]> = [
+ [layerFillFixedPlus1, "+1"],
+ [layerFillFixedBase, "Base"],
+ [layerFillFixedMinus1, "-1"],
+ [layerFillFixedMinus2, "-2"],
+ [layerFillFixedMinus3, "-3"],
+ [layerFillFixedMinus4, "-4"],
+ ];
+
+ private resolveLayerRecipes = (luminance: number): SwatchInfo[] => {
+ layerFillBaseLuminance.setValueFor(this.designSystemElement, luminance);
+
+ return this.layerTokens
+ .map(
+ (conf: [DesignToken, string]): SwatchInfo => {
+ const color = conf[0]
+ .getValueFor(this.designSystemElement)
+ .toColorString();
+ return {
+ index: this.neutralColors.indexOf(color),
+ color: color,
+ title: conf[1],
+ };
+ }
+ )
+ .reduce((accumulated: SwatchInfo[], value: SwatchInfo): Array => {
+ const colorIndex: number = accumulated.findIndex(
+ (config: SwatchInfo): boolean => config.color === value.color
+ );
+
+ return colorIndex === -1
+ ? accumulated.concat(value)
+ : accumulated.map(
+ (config: SwatchInfo, index: number): SwatchInfo =>
+ index === colorIndex
+ ? {
+ index: this.neutralColors.indexOf(value.color),
+ color: value.color,
+ title: value.title!.concat(", ", config.title!),
+ }
+ : config
+ );
+ }, [])
+ .sort((a: SwatchInfo, b: SwatchInfo): number => a.index - b.index);
+ };
+
+ private get lightModeLayers(): SwatchInfo[] {
+ return this.resolveLayerRecipes(LayerBaseLuminance.LightMode);
+ }
+
+ private get darkModeLayers(): SwatchInfo[] {
+ return this.resolveLayerRecipes(LayerBaseLuminance.DarkMode);
+ }
+
+ controlPaneHandler(e: CustomEvent) {
+ const detail: { field: string; value: any } = e.detail;
+ (this as any)[detail.field] = detail.value;
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/colors.ts b/packages/tooling/adaptive-ui-explorer/src/colors.ts
new file mode 100644
index 00000000000..e04324c7940
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/colors.ts
@@ -0,0 +1,25 @@
+export enum AccentColor {
+ black = "#000000",
+ white = "#FFFFFF",
+ magenta = "#DA1A5F",
+ blue = "#0078D4",
+ green = "#107C10",
+ purple = "#5C2D91",
+ orange = "#D83B01",
+ yellow = "#F2C812",
+}
+
+export const neutralColors: string[] = [
+ "#808080",
+ "#73818C",
+ "#718E71",
+ "#7F738C",
+ "#8C7A73",
+ "#0078D4",
+ "#107C10",
+ "#5C2D91",
+ "#D83B01",
+];
+
+export const defaultAccentColor: string = AccentColor.blue;
+export const defaultNeutralColor: string = neutralColors[0];
diff --git a/packages/tooling/adaptive-ui-explorer/src/component-type.ts b/packages/tooling/adaptive-ui-explorer/src/component-type.ts
new file mode 100644
index 00000000000..65833bb2a76
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/component-type.ts
@@ -0,0 +1,6 @@
+export enum ComponentType {
+ backplate = "backplate",
+ text = "text",
+ form = "form",
+ // sample = "sample",
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/adaptive-component.ts b/packages/tooling/adaptive-ui-explorer/src/components/adaptive-component.ts
new file mode 100644
index 00000000000..d1b51b75294
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/adaptive-component.ts
@@ -0,0 +1,117 @@
+import {
+ css,
+ customElement,
+ ElementViewTemplate,
+ FASTElement,
+ html,
+ observable,
+} from "@microsoft/fast-element";
+import { CSSDesignToken } from "@microsoft/fast-foundation";
+import { Swatch } from "@microsoft/adaptive-ui";
+
+// why this component is different
+
+function template(): ElementViewTemplate {
+ return html`
+
+
+
+ `;
+}
+
+const styles = css`
+ :host {
+ display: flex;
+ align-items: center;
+ padding: 6px 12px;
+ box-sizing: border-box;
+ font-size: 14px;
+ justify-items: start;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+
+ :host {
+ background: var(--ac-fill-rest);
+ border-color: var(--ac-stroke-rest) !important;
+ color: var(--ac-foreground-rest);
+ }
+
+ :host(:hover) {
+ background: var(--ac-fill-hover);
+ border-color: var(--ac-stroke-hover) !important;
+ color: var(--ac-foreground-hover);
+ }
+
+ :host(:active) {
+ background: var(--ac-fill-active);
+ border-color: var(--ac-stroke-active) !important;
+ color: var(--ac-foreground-active);
+ }
+
+ :host(:focus) {
+ background: var(--ac-fill-focus);
+ border-color: var(--ac-stroke-focus) !important;
+ color: var(--ac-foreground-focus);
+ }
+`;
+
+@customElement({
+ name: "app-adaptive-component",
+ template: template(),
+ styles,
+})
+export class AdaptiveComponent extends FASTElement {
+ @observable
+ public fillRest?: CSSDesignToken;
+
+ @observable
+ public fillHover?: CSSDesignToken;
+
+ @observable
+ public fillActive?: CSSDesignToken;
+
+ @observable
+ public fillFocus?: CSSDesignToken;
+
+ @observable
+ public strokeRest?: CSSDesignToken;
+
+ @observable
+ public strokeHover?: CSSDesignToken;
+
+ @observable
+ public strokeActive?: CSSDesignToken;
+
+ @observable
+ public strokeFocus?: CSSDesignToken;
+
+ @observable
+ public foregroundRest?: CSSDesignToken;
+
+ @observable
+ public foregroundHover?: CSSDesignToken;
+
+ @observable
+ public foregroundActive?: CSSDesignToken;
+
+ @observable
+ public foregroundFocus?: CSSDesignToken;
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/color-block.ts b/packages/tooling/adaptive-ui-explorer/src/components/color-block.ts
new file mode 100644
index 00000000000..7ff6d05e634
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/color-block.ts
@@ -0,0 +1,558 @@
+import { parseColorHexRGB } from "@microsoft/fast-colors";
+import {
+ attr,
+ css,
+ customElement,
+ DOM,
+ FASTElement,
+ html,
+ ViewTemplate,
+ when,
+} from "@microsoft/fast-element";
+import { display } from "@microsoft/fast-foundation";
+import {
+ accentFillActive,
+ accentFillFocus,
+ accentFillHover,
+ accentFillRest,
+ accentForegroundActive,
+ accentForegroundFocus,
+ accentForegroundHover,
+ accentForegroundRest,
+ fillColor,
+ focusStrokeInner,
+ focusStrokeOuter,
+ foregroundOnAccentActive,
+ foregroundOnAccentFocus,
+ foregroundOnAccentHover,
+ foregroundOnAccentRest,
+ neutralFillActive,
+ neutralFillFocus,
+ neutralFillHover,
+ neutralFillInputActive,
+ neutralFillInputFocus,
+ neutralFillInputHover,
+ neutralFillInputRest,
+ neutralFillRest,
+ neutralFillStealthActive,
+ neutralFillStealthFocus,
+ neutralFillStealthHover,
+ neutralFillStealthRest,
+ neutralFillStrongActive,
+ neutralFillStrongHover,
+ neutralFillStrongRest,
+ neutralForegroundActive,
+ neutralForegroundFocus,
+ neutralForegroundHint,
+ neutralForegroundHover,
+ neutralForegroundRest,
+ neutralStrokeActive,
+ neutralStrokeDividerRest,
+ neutralStrokeFocus,
+ neutralStrokeHover,
+ neutralStrokeRest,
+ neutralStrokeStrongActive,
+ neutralStrokeStrongFocus,
+ neutralStrokeStrongHover,
+ neutralStrokeStrongRest,
+ SwatchRGB,
+} from "@microsoft/adaptive-ui";
+import { ComponentType } from "../component-type.js";
+import "./adaptive-component.js";
+import "./swatch.js";
+
+const backplateComponents = html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+const textComponents = html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+const formComponents = html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+const template = html`
+
+ SWATCH ${x => x.index} - ${x => x.color.toUpperCase()}
+ ${when(
+ x => x.layerName,
+ html`
+
+ Layer: ${x => x.layerName}
+
+ `
+ )}
+
+
+
+ ${x => x.componentTypeTemplate()}
+
+`;
+
+const styles = css`
+ ${display("flex")} :host {
+ flex-direction: column;
+ flex-grow: 1;
+ align-items: stretch;
+ text-align: center;
+ position: relative;
+ transition: opacity 0.1s linear;
+ height: 100%;
+ min-height: 100%;
+ background-color: ${fillColor};
+ color: ${neutralForegroundRest};
+ }
+
+ .title {
+ margin: 16px auto 4px;
+ font-weight: 600;
+ height: 34px;
+ color: ${neutralForegroundHint};
+ }
+
+ .title code {
+ font-weight: normal;
+ }
+
+ .content {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 0 48px 36px;
+ }
+
+ .example {
+ height: 60px;
+ display: flex;
+ align-items: center;
+ margin-top: 24px;
+ }
+
+ .divider {
+ width: 150px;
+ }
+`;
+
+@customElement({
+ name: "app-color-block",
+ template,
+ styles,
+})
+export class ColorBlock extends FASTElement {
+ @attr index: number;
+
+ @attr component: ComponentType;
+
+ @attr color: string;
+ private colorChanged(): void {
+ DOM.queueUpdate(() => this.updateColor());
+ }
+
+ @attr({ attribute: "layer-name" })
+ layerName: string;
+
+ componentTypeTemplate(): ViewTemplate {
+ switch (this.component) {
+ case ComponentType.backplate:
+ return backplateComponents;
+ case ComponentType.text:
+ return textComponents;
+ case ComponentType.form:
+ return formComponents;
+ }
+ }
+
+ private updateColor(): void {
+ if (this.color && this.$fastController.isConnected) {
+ const color = parseColorHexRGB(this.color)!;
+ fillColor.setValueFor(this, SwatchRGB.from(color));
+ }
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.styles.ts b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.styles.ts
new file mode 100644
index 00000000000..0da981848be
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.styles.ts
@@ -0,0 +1,38 @@
+import { css } from "@microsoft/fast-element";
+import { display } from "@microsoft/fast-foundation";
+import { typeRampPlus1FontSize, typeRampPlus1LineHeight } from "@microsoft/adaptive-ui";
+
+export const controlPaneStyles = css`
+ ${display("flex")} :host {
+ flex: 0 1 auto;
+ flex-direction: column;
+ gap: 24px;
+ }
+
+ .title {
+ font-size: ${typeRampPlus1FontSize};
+ line-height: ${typeRampPlus1LineHeight};
+ }
+
+ .radio-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ label {
+ align-items: flex-start;
+ display: flex;
+ }
+
+ label span {
+ margin-top: 3px;
+ }
+
+ input[type="checkbox"],
+ input[type="radio"] {
+ width: 16px;
+ height: 16px;
+ margin-right: 8px;
+ }
+`;
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.template.ts b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.template.ts
new file mode 100644
index 00000000000..4321f84b42c
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.template.ts
@@ -0,0 +1,79 @@
+import { ElementViewTemplate, html, repeat } from "@microsoft/fast-element";
+import { ComponentType } from "../../component-type.js";
+import { ControlPane } from "./control-pane.js";
+
+function titleCase(str: string): string {
+ return str
+ .split("")
+ .reduce((accumulated: string, value: string, index: number): string => {
+ return accumulated.concat(index === 0 ? value.toUpperCase() : value);
+ }, "");
+}
+
+export function controlPaneTemplate(): ElementViewTemplate {
+ return html`
+ Settings
+
+
+ ${repeat(
+ x => Object.keys(ComponentType),
+ html`
+
+ `
+ )}
+
+
+
+ {
+ x.updateFormValue(
+ "neutralColor",
+ c.eventTarget().value
+ );
+ }}"
+ />
+
+
+
+
+ {
+ x.updateFormValue(
+ "accentColor",
+ c.eventTarget().value
+ );
+ }}"
+ />
+
+ `;
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.ts b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.ts
new file mode 100644
index 00000000000..1dc1baace88
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/control-pane.ts
@@ -0,0 +1,19 @@
+import { FASTElement, observable } from "@microsoft/fast-element";
+
+export class ControlPane extends FASTElement {
+ @observable
+ componentType: string;
+
+ @observable
+ accentColor: string;
+
+ @observable
+ neutralColor: string;
+
+ @observable
+ showOnlyLayerBackgrounds: boolean = true;
+
+ updateFormValue(field: string, value: any) {
+ this.$emit("formvaluechange", { field: field, value: value });
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/control-pane/index.ts b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/index.ts
new file mode 100644
index 00000000000..a442498d6c6
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/control-pane/index.ts
@@ -0,0 +1,11 @@
+import { ControlPane } from "./control-pane.js";
+import { controlPaneStyles as styles } from "./control-pane.styles.js";
+import { controlPaneTemplate as template } from "./control-pane.template.js";
+
+ControlPane.define({
+ name: "app-control-pane",
+ styles,
+ template: template(),
+});
+
+export { ControlPane } from "./control-pane.js";
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/index.ts b/packages/tooling/adaptive-ui-explorer/src/components/index.ts
new file mode 100644
index 00000000000..c4041c5befb
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/index.ts
@@ -0,0 +1,8 @@
+export * from "./control-pane/index.js";
+export * from "./layer-background/index.js";
+export * from "./palette-gradient/index.js";
+export * from "./sample-app/index.js";
+export * from "./sample-page/index.js";
+export * from "./adaptive-component.js";
+export * from "./color-block.js";
+export * from "./swatch.js";
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/layer-background/index.ts b/packages/tooling/adaptive-ui-explorer/src/components/layer-background/index.ts
new file mode 100644
index 00000000000..5abe2707d74
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/layer-background/index.ts
@@ -0,0 +1,131 @@
+import {
+ attr,
+ css,
+ ElementViewTemplate,
+ FASTElement,
+ html,
+ nullableNumberConverter,
+} from "@microsoft/fast-element";
+import {
+ DesignToken,
+ DesignTokenChangeRecord,
+ display,
+} from "@microsoft/fast-foundation";
+import {
+ fillColor,
+ layerFillBaseLuminance,
+ layerFillFixedBase,
+ layerFillFixedMinus1,
+ layerFillFixedMinus2,
+ layerFillFixedMinus3,
+ layerFillFixedMinus4,
+ layerFillFixedPlus1,
+ layerFillFixedPlus2,
+ layerFillFixedPlus3,
+ layerFillFixedPlus4,
+ layerPalette,
+ neutralForegroundRest,
+ Swatch,
+} from "@microsoft/adaptive-ui";
+
+export class LayerBackground extends FASTElement {
+ @attr({ attribute: "base-layer-luminance", converter: nullableNumberConverter })
+ public baseLayerLuminance: number = 1;
+ private baseLayerLuminanceChanged(prev: number, next: number): void {
+ layerFillBaseLuminance.setValueFor(this, this.baseLayerLuminance);
+ this.updateBackgroundColor();
+ }
+
+ @attr({ attribute: "background-layer-recipe" })
+ public backgroundLayerRecipe: string = "Base";
+ private backgroundLayerRecipeChanged(prev: string, next: string): void {
+ this.updateBackgroundColor();
+ }
+
+ private updateBackgroundColor(): void {
+ if (!this.$fastController.isConnected) {
+ return;
+ }
+
+ if (this.backgroundLayerRecipe !== undefined) {
+ let swatch: Swatch | null = null;
+ switch (this.backgroundLayerRecipe) {
+ case "-1":
+ swatch = layerFillFixedMinus1.getValueFor(this);
+ break;
+ case "-2":
+ swatch = layerFillFixedMinus2.getValueFor(this);
+ break;
+ case "-3":
+ swatch = layerFillFixedMinus3.getValueFor(this);
+ break;
+ case "-4":
+ swatch = layerFillFixedMinus4.getValueFor(this);
+ break;
+ case "Base":
+ swatch = layerFillFixedBase.getValueFor(this);
+ break;
+ case "+1":
+ swatch = layerFillFixedPlus1.getValueFor(this);
+ break;
+ case "+2":
+ swatch = layerFillFixedPlus2.getValueFor(this);
+ break;
+ case "+3":
+ swatch = layerFillFixedPlus3.getValueFor(this);
+ break;
+ case "+4":
+ swatch = layerFillFixedPlus4.getValueFor(this);
+ break;
+ }
+
+ if (swatch !== null) {
+ fillColor.setValueFor(this, swatch);
+ }
+ }
+ }
+
+ public handleChange(
+ token: DesignToken,
+ record: DesignTokenChangeRecord
+ ): void {
+ if (record.target === this && token === layerPalette) {
+ this.updateBackgroundColor();
+ }
+ }
+
+ public connectedCallback(): void {
+ super.connectedCallback();
+
+ layerPalette.subscribe(this);
+
+ this.updateBackgroundColor();
+ }
+
+ public disconnectedCallback(): void {
+ super.disconnectedCallback();
+
+ layerPalette.unsubscribe(this);
+ }
+}
+
+export function layerBackgroundTemplate(): ElementViewTemplate<
+ T
+> {
+ return html`
+
+ `;
+}
+
+export const layerBackgroundStyles = css`
+ ${display("block")} :host {
+ background: ${fillColor};
+ color: ${neutralForegroundRest};
+ }
+`;
+
+LayerBackground.define({
+ name: "app-layer-background",
+ styles: layerBackgroundStyles,
+ template: layerBackgroundTemplate(),
+});
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/index.ts b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/index.ts
new file mode 100644
index 00000000000..58a71bbc735
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/index.ts
@@ -0,0 +1,3 @@
+export { PaletteGradient } from "./palette-gradient.js";
+export { paletteGradientStyles as styles } from "./palette-gradient.styles.js";
+export { paletteGradientTemplate as template } from "./palette-gradient.template.js";
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.styles.ts b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.styles.ts
new file mode 100644
index 00000000000..6508e588e03
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.styles.ts
@@ -0,0 +1,38 @@
+import { css } from "@microsoft/fast-element";
+import { display } from "@microsoft/fast-foundation";
+
+export const paletteGradientStyles = css`
+ ${display("flex")} :host {
+ display: flex;
+ width: 100%;
+ }
+
+ a {
+ display: flex;
+ flex: 1;
+ height: 100%;
+ }
+
+ a.source {
+ position: relative;
+ }
+
+ a.source::before {
+ width: 6px;
+ height: 6px;
+ margin: 0 auto;
+ content: "";
+ opacity: 0.7;
+ position: relative;
+ border: solid 1px currentcolor;
+ border-radius: 50%;
+ display: block;
+ align-self: center;
+ }
+
+ a.closest::before {
+ content: "~";
+ border: none;
+ line-height: 6px;
+ }
+`;
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts
new file mode 100644
index 00000000000..205119d14bf
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.template.ts
@@ -0,0 +1,32 @@
+import { html, repeat } from "@microsoft/fast-element";
+import { isDark, Swatch } from "@microsoft/adaptive-ui";
+import { PaletteGradient } from "./palette-gradient.js";
+
+function getClass(swatch: Swatch, source?: Swatch, closestSource?: Swatch) {
+ return swatch.toColorString() === source?.toColorString()
+ ? "source"
+ : swatch.toColorString() === closestSource?.toColorString()
+ ? "source closest"
+ : "";
+}
+
+function getColor(background: Swatch) {
+ const darkMode = isDark(background);
+ return darkMode ? "white" : "black";
+}
+
+export const paletteGradientTemplate = html`
+ ${repeat(
+ x => x.palette?.swatches || [],
+ html`
+
+ `,
+ { positioning: true }
+ )}
+`;
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts
new file mode 100644
index 00000000000..14f20cd4584
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/palette-gradient/palette-gradient.ts
@@ -0,0 +1,21 @@
+import { Palette, Swatch } from "@microsoft/adaptive-ui";
+import { customElement, FASTElement, observable } from "@microsoft/fast-element";
+import { paletteGradientStyles as styles } from "./palette-gradient.styles.js";
+import { paletteGradientTemplate as template } from "./palette-gradient.template.js";
+
+@customElement({
+ name: "app-palette-gradient",
+ template,
+ styles,
+})
+export class PaletteGradient extends FASTElement {
+ closestSource?: Swatch;
+
+ @observable
+ palette?: Palette;
+ private paletteChanged() {
+ this.closestSource = this.palette?.get(
+ this.palette?.closestIndexOf(this.palette?.source)
+ );
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-app/index.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/index.ts
new file mode 100644
index 00000000000..4d53f52299e
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/index.ts
@@ -0,0 +1,9 @@
+import { SampleApp } from "./sample-app.js";
+import { sampleAppStyles as styles } from "./sample-app.styles.js";
+import { sampleAppTemplate as template } from "./sample-app.template.js";
+
+SampleApp.define({
+ name: "app-sample-app",
+ styles,
+ template: template(),
+});
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.styles.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.styles.ts
new file mode 100644
index 00000000000..cdceac418e0
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.styles.ts
@@ -0,0 +1,179 @@
+import { css } from "@microsoft/fast-element";
+import { display, forcedColorsStylesheetBehavior } from "@microsoft/fast-foundation";
+import { SystemColors } from "@microsoft/fast-web-utilities";
+import {
+ bodyFont,
+ controlCornerRadius,
+ designUnit,
+ fillColor,
+ neutralForegroundHint,
+ neutralForegroundRest,
+ typeRampMinus2FontSize,
+ typeRampPlus3FontSize,
+ typeRampPlus3LineHeight,
+} from "@microsoft/adaptive-ui";
+
+export const sampleAppStyles = css`
+ ${display("flex")}
+
+ :host {
+ flex-direction: column;
+ font-family: ${bodyFont};
+ color: ${neutralForegroundRest};
+ box-sizing: border-box;
+ min-height: 650px;
+ min-width: 775px;
+ background: ${fillColor};
+ border-radius: calc(${controlCornerRadius} * 1px);
+ --gutter: 20;
+ }
+
+ app-layer-background {
+ display: flex;
+ flex-grow: 1;
+ }
+
+ p {
+ margin: 0;
+ }
+
+ .icon {
+ pointer-events: none;
+ }
+
+ .wrapper {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ position: relative;
+ }
+
+ .toolbar {
+ display: flex;
+ align-items: center;
+ box-sizing: border-box;
+ height: 40px;
+ padding: 0 12px;
+ }
+
+ fluent-tabs {
+ flex-grow: 1;
+ }
+
+ fluent-tabs::part(tablist) {
+ padding: 0 4px;
+ align-self: start;
+ }
+
+ fluent-tabs::part(activeIndicator) {
+ margin: 0;
+ }
+
+ fluent-tab {
+ padding: calc(${designUnit} * 5px) calc(${designUnit} * 3px);
+ }
+
+ fluent-tab-panel {
+ padding: 0;
+ height: 100%;
+ }
+
+ .content {
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ text-align: start;
+ box-shadow: none;
+ }
+
+ .pane {
+ width: 240px;
+ }
+
+ .pane > fluent-listbox {
+ width: 100%;
+ }
+
+ .details {
+ height: unset;
+ box-shadow: none;
+ }
+
+ /* wrapper, toolbar, content, pane, details */
+
+ .content .heading {
+ font-size: ${typeRampPlus3FontSize};
+ line-height: ${typeRampPlus3LineHeight};
+ margin: 0;
+ margin-bottom: 10px;
+ font-weight: bold;
+ }
+
+ .icon {
+ fill: currentColor;
+ }
+
+ .saturation-slider-track {
+ height: 100%;
+ border-radius: calc(${controlCornerRadius} * 1px);
+ }
+
+ .hue-slider-track {
+ height: 100%;
+ border-radius: calc(${controlCornerRadius} * 1px);
+ background-image:
+ linear-gradient(
+ to right,
+ rgb(255, 0, 0),
+ rgb(255, 77, 0),
+ rgb(255, 153, 0),
+ rgb(255, 230, 0),
+ rgb(204, 255, 0),
+ rgb(128, 255, 0),
+ rgb(51, 255, 0),
+ rgb(0, 255, 26),
+ rgb(0, 255, 102),
+ rgb(0, 255, 179),
+ rgb(0, 255, 255),
+ rgb(0, 179, 255),
+ rgb(0, 102, 255),
+ rgb(0, 26, 255),
+ rgb(51, 0, 255),
+ rgb(128, 0, 255),
+ rgb(204, 0, 255),
+ rgb(255, 0, 230),
+ rgb(255, 0, 153),
+ rgb(255, 0, 76),
+ rgb(255, 0, 4)
+ );"
+ }
+
+ .responsive-expand-flipper {
+ position: absolute;
+ left: -30px;
+ align-self: center;
+ display: none;
+ visibility: hidden;
+ }
+
+ site-color-swatch {
+ margin: 0;
+ }
+
+ fluent-slider-label {
+ font-size: ${typeRampMinus2FontSize};
+ color: ${neutralForegroundHint};
+ }
+`.withBehaviors(
+ forcedColorsStylesheetBehavior(
+ css`
+ .text-container {
+ color: ${SystemColors.ButtonText};
+ }
+ fluent-tab:hover[aria-selected="true"] {
+ background: ${SystemColors.Highlight};
+ fill: ${SystemColors.HighlightText};
+ }
+ `
+ )
+);
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.template.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.template.ts
new file mode 100644
index 00000000000..c2963bdfb62
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.template.ts
@@ -0,0 +1,118 @@
+import { ElementViewTemplate, html } from "@microsoft/fast-element";
+import { SampleApp } from "./sample-app.js";
+// import DataAreaIcon from "@fluentui/svg-icons/icons/data_area_24_regular.svg";
+// import DataHistogramIcon from "@fluentui/svg-icons/icons/data_histogram_24_regular.svg";
+// import DataScatterIcon from "@fluentui/svg-icons/icons/data_scatter_24_regular.svg";
+
+export function sampleAppTemplate(): ElementViewTemplate {
+ return html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Root item 1
+
+
+ Flowers
+ Daisy
+
+ Sunflower
+
+
+ Rose
+
+ Pink
+ Red
+ White
+
+
+ Nested item 2
+ Nested item 3
+
+
+ Root item 2
+
+ Flowers
+
+
+ Daisy
+
+ Sunflower
+ Rose
+
+ Nested item 2
+ Nested item 3
+
+ Root item 3
+
+
+
+
+
+
+
+
+
+ Item 1
+ Item 2
+ Item 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.ts
new file mode 100644
index 00000000000..f4be048ca1b
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-app/sample-app.ts
@@ -0,0 +1,3 @@
+import { FASTElement } from "@microsoft/fast-element";
+
+export class SampleApp extends FASTElement {}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-page/index.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/index.ts
new file mode 100644
index 00000000000..5e7856f3efc
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/index.ts
@@ -0,0 +1,9 @@
+import { SamplePage } from "./sample-page.js";
+import { samplePageStyles as styles } from "./sample-page.styles.js";
+import { samplePageTemplate as template } from "./sample-page.template.js";
+
+SamplePage.define({
+ name: "app-sample-page",
+ styles,
+ template: template(),
+});
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.styles.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.styles.ts
new file mode 100644
index 00000000000..7ceedb92735
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.styles.ts
@@ -0,0 +1,137 @@
+import { css } from "@microsoft/fast-element";
+import { display } from "@microsoft/fast-foundation";
+import {
+ accentFillRest,
+ controlCornerRadius,
+ designUnit,
+ neutralForegroundRest,
+} from "@microsoft/adaptive-ui";
+
+export const samplePageStyles = css`
+ ${display("flex")} :host {
+ display: grid;
+ grid-gap: calc(var(--gutter) * 2px);
+ grid-template-columns: auto 300px;
+ padding: calc(var(--gutter) * 2px);
+ position: relative;
+ border-radius: 0 calc(${controlCornerRadius} * 1px)
+ calc(${controlCornerRadius} * 1px) 0;
+ }
+
+ .image-container {
+ /** Temp background */
+ background: #d6d6d6;
+ width: 100%;
+ height: 215px;
+ display: flex;
+ }
+
+ .badge {
+ align-self: flex-end;
+ margin: calc(var(--gutter) * 1px);
+ }
+
+ .text-container {
+ display: flex;
+ flex-direction: column;
+ padding: calc(var(--gutter) * 1px);
+ text-align: start;
+ color: ${neutralForegroundRest};
+ }
+
+ .sample-control {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ }
+
+ .sample-control-actions {
+ margin-inline-start: auto;
+ }
+
+ .sample-control-text {
+ margin-inline-start: calc(${designUnit} * 2px + 2px);
+ }
+
+ .sample-control-icon {
+ width: 21px;
+ height: 21px;
+ background-color: ${accentFillRest};
+ border-radius: calc(${controlCornerRadius} * 1px);
+ }
+
+ .preview-controls {
+ display: grid;
+ grid-auto-rows: max-content;
+ grid-gap: 20px;
+ }
+
+ .control-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 20px;
+ }
+
+ .control-container-2 {
+ display: grid;
+ grid-template-columns: 1fr auto auto;
+ grid-gap: 20px;
+ }
+
+ .control-container p {
+ margin-inline-start: calc(${designUnit} * 2px + 2px);
+ }
+
+ .control-container-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ text-align: start;
+ color: ${neutralForegroundRest};
+ }
+
+ .checkbox {
+ grid-row: 2;
+ }
+
+ .checkbox-label {
+ grid-row: 2;
+ grid-column: 2;
+ }
+
+ fluent-card {
+ width: 280px;
+ }
+
+ fluent-badge {
+ --badge-fill-primary: #e4bc11;
+ --badge-color-primary: #000000;
+ }
+
+ fluent-slider {
+ min-width: unset;
+ }
+
+ fluent-tab-panel {
+ height: 100%;
+ }
+
+ fluent-tab[aria-selected="true"] {
+ background: transparent;
+ }
+
+ fluent-radio-group.example-radios {
+ margin: 0;
+ }
+
+ fluent-radio-group.example-radios::part(positioning-region) {
+ display: grid;
+ grid-template-columns: auto;
+ height: 100%;
+ }
+
+ fluent-radio-group.swatches::part(positioning-region) {
+ display: grid;
+ grid-gap: 10px;
+ grid-auto-flow: column;
+ }
+`;
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.template.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.template.ts
new file mode 100644
index 00000000000..e0d6b5666f9
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.template.ts
@@ -0,0 +1,122 @@
+import { ElementViewTemplate, html } from "@microsoft/fast-element";
+import { SamplePage } from "./sample-page.js";
+// import MoreIcon from "@fluentui/svg-icons/icons/more_horizontal_20_regular.svg";
+// import DownloadIcon from "@fluentui/svg-icons/icons/arrow_download_20_regular.svg";
+// import PlayIcon from "@fluentui/svg-icons/icons/play_20_regular.svg";
+
+export function samplePageTemplate(): ElementViewTemplate {
+ return html`
+
+
+
+ Badge
+
+
+
+
Example card
+
+ At purus lectus quis habitant commodo, cras. Aliquam malesuada velit a
+ tortor. Felis orci tellus netus risus et ultricies augue aliquet.
+ Suscipit mattis mus amet nibh...
+
+
+
+
+
+
+
+
+
+ Menu item 1
+
+
+ Menu item 2
+
+
+ Menu item 3
+
+
+
+ Menu item 4
+
+
+
+
+ Radio 1
+ Radio 2
+
+
+ Toggle
+
+ Checkbox
+
+
+
+
+
+
+
+
+
+
+
+ Button
+
+
+
+
+
+ Button
+
+
+
+
+
+
+ `;
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.ts b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.ts
new file mode 100644
index 00000000000..1efd0a5c7a7
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/sample-page/sample-page.ts
@@ -0,0 +1,3 @@
+import { FASTElement } from "@microsoft/fast-element";
+
+export class SamplePage extends FASTElement {}
diff --git a/packages/tooling/adaptive-ui-explorer/src/components/swatch.ts b/packages/tooling/adaptive-ui-explorer/src/components/swatch.ts
new file mode 100644
index 00000000000..a233ac083ac
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/components/swatch.ts
@@ -0,0 +1,217 @@
+import { contrastRatio, parseColor } from "@microsoft/fast-colors";
+import {
+ attr,
+ css,
+ customElement,
+ ElementViewTemplate,
+ FASTElement,
+ html,
+ observable,
+} from "@microsoft/fast-element";
+import { DesignToken } from "@microsoft/fast-foundation";
+import { fillColor, neutralForegroundHint, Swatch } from "@microsoft/adaptive-ui";
+
+export enum SwatchType {
+ fill = "fill",
+ foreground = "foreground",
+ outline = "outline",
+}
+
+function template(): ElementViewTemplate {
+ return html`
+
+ ${x => x.recipeName}
+ ${x => x.colorValue}
+ `;
+}
+
+const styles = css`
+ :host {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ grid-template-rows: auto;
+ align-items: center;
+ width: 100%;
+ padding: 4px 0;
+ box-sizing: border-box;
+ color: ${neutralForegroundHint};
+ font-size: 12px;
+ grid-column-gap: 16px;
+ justify-items: start;
+ }
+
+ :host([type="foreground"]) .icon {
+ border: 1px solid black;
+ }
+
+ :host([type="foreground"]) .icon::before {
+ font-size: 13px;
+ content: "A";
+ font-weight: 400;
+ }
+
+ .icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ border-radius: 2px;
+ box-sizing: border-box;
+ }
+
+ .recipe-name {
+ grid-column: 2;
+ grid-row: 1;
+ }
+
+ .hex-code {
+ grid-column: 3;
+ grid-row: 1;
+ }
+`;
+
+@customElement({
+ name: "app-swatch",
+ template: template(),
+ styles,
+})
+export class AppSwatch extends FASTElement {
+ @attr
+ public type: SwatchType;
+
+ @attr({ attribute: "recipe-name" })
+ public recipeName: string;
+
+ @observable
+ public foregroundRecipe?: DesignToken;
+ public foregroundRecipeChanged() {
+ this.updateObservables();
+ }
+
+ @observable
+ public fillRecipe?: DesignToken;
+ public fillRecipeChanged() {
+ this.updateObservables();
+ }
+
+ @observable
+ public outlineRecipe?: DesignToken;
+ public outlineRecipeChanged() {
+ this.updateObservables();
+ }
+
+ @observable
+ public iconStyle: string;
+
+ @observable
+ public contrastMessage: string;
+
+ @observable
+ public colorValue: string;
+
+ public connectedCallback() {
+ super.connectedCallback();
+
+ fillColor.subscribe(this);
+
+ this.updateObservables();
+ }
+
+ public disconnectedCallback() {
+ super.disconnectedCallback();
+
+ fillColor.unsubscribe(this);
+ }
+
+ public handleChange() {
+ this.updateObservables();
+ }
+
+ private updateObservables() {
+ this.updateIconStyle();
+ this.updateContrastMessage();
+ this.updateColorValue();
+ }
+
+ private tokenCSS(token?: DesignToken): string {
+ return token && typeof (token as any).createCSS === "function"
+ ? (token as any).createCSS()
+ : "";
+ }
+
+ private evaluateToken(token?: DesignToken): string {
+ return token?.getValueFor(this).toColorString() || "";
+ }
+
+ private updateIconStyle(): void {
+ const background = `background-color: ${this.tokenCSS(this.fillRecipe)}`;
+ this.iconStyle =
+ this.type === SwatchType.outline
+ ? `border: 4px solid ${this.tokenCSS(this.outlineRecipe)}; ${background}`
+ : this.type === SwatchType.foreground
+ ? `color: ${this.tokenCSS(this.foregroundRecipe)}; ${background}`
+ : background;
+ }
+
+ private formatContrast(a?: DesignToken, b?: DesignToken): string {
+ return a && b
+ ? contrastRatio(
+ parseColor(this.evaluateToken(a))!,
+ parseColor(this.evaluateToken(b))!
+ ).toFixed(2)
+ : "";
+ }
+
+ private formatBackgroundContrast(
+ a?: DesignToken,
+ b?: DesignToken
+ ): string {
+ return `BG contrast: ${this.formatContrast(a, b)} : 1`;
+ }
+
+ private formatForegroundContrast(
+ a?: DesignToken,
+ b?: DesignToken
+ ): string {
+ return `Text contrast: ${this.formatContrast(a, b)} : 1`;
+ }
+
+ private updateContrastMessage(): void {
+ const backgroundContrastMessage: string = this.formatBackgroundContrast(
+ this.type === SwatchType.foreground
+ ? this.foregroundRecipe
+ : this.type === SwatchType.outline
+ ? this.outlineRecipe
+ : this.fillRecipe,
+ this.type === SwatchType.foreground || this.type === SwatchType.outline
+ ? this.fillRecipe
+ : fillColor
+ );
+
+ this.contrastMessage =
+ this.type === SwatchType.fill
+ ? backgroundContrastMessage.concat(
+ "\n",
+ this.formatForegroundContrast(
+ this.fillRecipe,
+ this.foregroundRecipe
+ )
+ )
+ : backgroundContrastMessage;
+ }
+
+ private updateColorValue(): void {
+ const recipe =
+ this.type === SwatchType.outline
+ ? this.outlineRecipe
+ : this.type === SwatchType.foreground
+ ? this.foregroundRecipe
+ : this.fillRecipe;
+ this.colorValue = this.evaluateToken(recipe).toUpperCase();
+ }
+}
diff --git a/packages/tooling/adaptive-ui-explorer/src/index.ts b/packages/tooling/adaptive-ui-explorer/src/index.ts
new file mode 100644
index 00000000000..78c60d0e3c5
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/src/index.ts
@@ -0,0 +1,3 @@
+export * from "./components/index.js";
+export * from "./app.js";
+export * from "./component-type.js";
diff --git a/packages/tooling/adaptive-ui-explorer/tsconfig.json b/packages/tooling/adaptive-ui-explorer/tsconfig.json
new file mode 100644
index 00000000000..8b46cab95fc
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "declarationDir": "dist/dts",
+ "outDir": "dist/esm",
+ "experimentalDecorators": true,
+ "strictPropertyInitialization": false,
+ "target": "es2015",
+ "module": "ESNext",
+ "moduleResolution": "Node16",
+ "importHelpers": true,
+ "jsx": "react",
+ "types": ["webpack-env"],
+ "lib": ["DOM", "ES2015", "ES2016.Array.Include", "ES2017.Object"]
+ },
+ "include": ["src"]
+}
diff --git a/packages/tooling/adaptive-ui-explorer/webpack.config.cjs b/packages/tooling/adaptive-ui-explorer/webpack.config.cjs
new file mode 100644
index 00000000000..f73563850f8
--- /dev/null
+++ b/packages/tooling/adaptive-ui-explorer/webpack.config.cjs
@@ -0,0 +1,56 @@
+const { CleanWebpackPlugin } = require("clean-webpack-plugin");
+const InjectBodyPlugin = require("inject-body-webpack-plugin").default;
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const ResolveTypeScriptPlugin = require("resolve-typescript-plugin");
+const path = require('path');
+
+module.exports = function(env, { mode }) {
+ const production = mode === 'production';
+ return {
+ mode: production ? 'production' : 'development',
+ devtool: production ? 'source-map' : 'inline-source-map',
+ entry: {
+ app: ['./src/app.ts']
+ },
+ output: {
+ filename: 'bundle.js',
+ publicPath: '/',
+ path: path.resolve(__dirname, './dist'),
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new InjectBodyPlugin({
+ content: " ",
+ }),
+ new HtmlWebpackPlugin({
+ title: "Adaptive UI Explorer",
+ }),
+ ],
+ devServer: {
+ static: {
+ directory: "./src/public",
+ },
+ open: true,
+ port: 7700,
+ allowedHosts: "all",
+ },
+ module: {
+ rules: [
+ {
+ test: /\.ts$/i,
+ use: [
+ {
+ loader: 'ts-loader'
+ }
+ ],
+ exclude: /node_modules/
+ }
+ ]
+ },
+ resolve: {
+ extensions: ['.ts', '.js'],
+ modules: ['src', 'node_modules'],
+ plugins: [new ResolveTypeScriptPlugin()]
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index a32ec939399..cac16515225 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9890,7 +9890,7 @@ debug@3.2.6:
dependencies:
ms "^2.1.1"
-debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
+debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -13427,6 +13427,15 @@ init-package-json@^3.0.2:
validate-npm-package-license "^3.0.4"
validate-npm-package-name "^4.0.0"
+inject-body-webpack-plugin@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/inject-body-webpack-plugin/-/inject-body-webpack-plugin-1.3.0.tgz#e29fee59c81f0170d01191d197fda375a5c4cf12"
+ integrity sha512-P2H7b52je3jKVcJ8Gw2MjqccGo0QutEjoXoB3XRKbSgsqOv3NgTypqoUm5D8oEz90vx5AIgW9Bk+hJxzEoGvMg==
+ dependencies:
+ debug "^4.3.1"
+ insert-string-after "^1.0.0"
+ insert-string-before "^1.0.0"
+
inline-style-parser@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
@@ -13472,6 +13481,16 @@ inquirer@^8.2.4:
through "^2.3.6"
wrap-ansi "^7.0.0"
+insert-string-after@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/insert-string-after/-/insert-string-after-1.0.0.tgz#084e1e9f4cdbc6aeb961bca3e8dce9a7e78908b0"
+ integrity sha512-v8P35PHcglmeE6sj12AzhJfRyrH221uA9iSO4VNWf/TQCqgvb/g1Zst6HpLxFrsTVhuVHzgZPPyhJZX1YJZ3yw==
+
+insert-string-before@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/insert-string-before/-/insert-string-before-1.0.0.tgz#b726537bb361d9ba72f1fcfcbf20d14521fa3808"
+ integrity sha512-Z3sKgwLX+x22HyEUjHJe05GdetR3aYtJy17cXRaym57AwXwI8Z7dCbAiYB+3d8u9NW/qi8ZLNhTev29H7QXnXw==
+
internal-slot@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"