From c67565c2cbb60d38a7769130712413a2b65d7378 Mon Sep 17 00:00:00 2001
From: Vikas Awaghade <67629551+vikas-cldcvr@users.noreply.github.com>
Date: Mon, 4 Dec 2023 18:41:14 +0530
Subject: [PATCH] FLOW-970 f-color-picker component (#203)
* FLOW-970 f-color-picker component created
* FLOW-970 playground controls added
* FLOW-970 playground controls added
* FLOW-970 color picker integrated with form builder
* FLOW-970 unit tests added
* FLOW-970 changelog updated
---
packages/flow-core/CHANGELOG.md | 6 +
packages/flow-core/package.json | 5 +-
.../f-color-picker/f-color-picker-global.scss | 9 +
.../f-color-picker/f-color-picker.scss | 35 ++++
.../f-color-picker/f-color-picker.test.ts | 28 +++
.../f-color-picker/f-color-picker.ts | 197 ++++++++++++++++++
.../flow-core/src/components/f-div/f-div.ts | 16 +-
.../components/f-form-field/f-form-field.ts | 9 +-
packages/flow-core/src/index.ts | 1 +
packages/flow-core/vite.config.ts | 9 +-
packages/flow-form-builder/CHANGELOG.md | 6 +
packages/flow-form-builder/package.json | 2 +-
.../components/f-form-builder/fields/color.ts | 35 ++++
.../components/f-form-builder/fields/index.ts | 4 +-
.../flow-form-builder/src/modules/helpers.ts | 6 +-
packages/flow-form-builder/src/types.ts | 11 +-
pnpm-lock.yaml | 7 +
stories/flow-core/f-color-picker.stories.ts | 169 +++++++++++++++
.../flow-form-builder/f-formbuilder-field.ts | 28 ++-
19 files changed, 551 insertions(+), 32 deletions(-)
create mode 100644 packages/flow-core/src/components/f-color-picker/f-color-picker-global.scss
create mode 100644 packages/flow-core/src/components/f-color-picker/f-color-picker.scss
create mode 100644 packages/flow-core/src/components/f-color-picker/f-color-picker.test.ts
create mode 100644 packages/flow-core/src/components/f-color-picker/f-color-picker.ts
create mode 100644 packages/flow-form-builder/src/components/f-form-builder/fields/color.ts
create mode 100644 stories/flow-core/f-color-picker.stories.ts
diff --git a/packages/flow-core/CHANGELOG.md b/packages/flow-core/CHANGELOG.md
index 629c7ed67..696fa6418 100644
--- a/packages/flow-core/CHANGELOG.md
+++ b/packages/flow-core/CHANGELOG.md
@@ -2,6 +2,12 @@
# Change Log
+## [2.6.0] - 2023-12-04
+
+### Minor Changes
+
+- `f-color-picker` element added.
+
## [2.5.0] - 2023-11-27
### Minor Changes
diff --git a/packages/flow-core/package.json b/packages/flow-core/package.json
index 970ba3436..fe734e655 100644
--- a/packages/flow-core/package.json
+++ b/packages/flow-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@cldcvr/flow-core",
- "version": "2.5.0",
+ "version": "2.6.0",
"description": "Core package of flow design system",
"module": "dist/flow-core.es.js",
"main": "dist/flow-core.cjs.js",
@@ -32,7 +32,8 @@
"lit": "^3.1.0",
"lodash-es": "^4.17.21",
"rxjs": "^7.5.7",
- "validate-color": "^2.2.1"
+ "validate-color": "^2.2.1",
+ "vanilla-colorful": "^0.7.2"
},
"devDependencies": {
"@custom-elements-manifest/analyzer": "^0.8.1",
diff --git a/packages/flow-core/src/components/f-color-picker/f-color-picker-global.scss b/packages/flow-core/src/components/f-color-picker/f-color-picker-global.scss
new file mode 100644
index 000000000..45585d06e
--- /dev/null
+++ b/packages/flow-core/src/components/f-color-picker/f-color-picker-global.scss
@@ -0,0 +1,9 @@
+@import "./../../mixins/scss/mixins";
+
+f-color-picker {
+ @include base();
+ width: fit-content;
+ height: fit-content;
+ max-width: 100%;
+ max-height: 100%;
+}
diff --git a/packages/flow-core/src/components/f-color-picker/f-color-picker.scss b/packages/flow-core/src/components/f-color-picker/f-color-picker.scss
new file mode 100644
index 000000000..43344888a
--- /dev/null
+++ b/packages/flow-core/src/components/f-color-picker/f-color-picker.scss
@@ -0,0 +1,35 @@
+@use "sass:map";
+$states: (
+ "primary": (
+ "border": 1px solid var(--color-primary-default)
+ ),
+ "default": (
+ "border": none
+ ),
+ "success": (
+ "border": 1px solid var(--color-success-default)
+ ),
+ "warning": (
+ "border": 1px solid var(--color-warning-default)
+ ),
+ "danger": (
+ "border": 1px solid var(--color-danger-default)
+ )
+);
+
+:host {
+ #f-color-picker-input {
+ cursor: pointer;
+ &.focused {
+ border: 1px solid var(--color-primary-default);
+ }
+ &[read-only] {
+ cursor: default;
+ }
+ @each $state, $color in $states {
+ &[border-state="#{$state}"]:not(.focused) {
+ border: map.get($color, "border");
+ }
+ }
+ }
+}
diff --git a/packages/flow-core/src/components/f-color-picker/f-color-picker.test.ts b/packages/flow-core/src/components/f-color-picker/f-color-picker.test.ts
new file mode 100644
index 000000000..5ade58917
--- /dev/null
+++ b/packages/flow-core/src/components/f-color-picker/f-color-picker.test.ts
@@ -0,0 +1,28 @@
+import { html, fixture, expect } from "@open-wc/testing";
+
+// import flow-core elements
+import "@cldcvr/flow-core";
+
+import { FColorPicker, FInput } from "@cldcvr/flow-core";
+
+describe("f-color-picker", () => {
+ // check if component is defined
+ it("is defined", () => {
+ const el = document.createElement("f-color-picker");
+ expect(el).instanceOf(FColorPicker);
+ });
+ it("should render with default attributes", async () => {
+ const el = await fixture(html``);
+
+ expect(el.getAttribute("variant")).to.equal("curved");
+ expect(el.getAttribute("size")).to.equal("medium");
+ expect(el.getAttribute("state")).to.equal("default");
+ });
+
+ it("should display color value in input box", async () => {
+ const el = await fixture(html``);
+
+ const input = el.shadowRoot?.querySelector("#hex-input");
+ expect(input.value).to.equal("#CCCCCC");
+ });
+});
diff --git a/packages/flow-core/src/components/f-color-picker/f-color-picker.ts b/packages/flow-core/src/components/f-color-picker/f-color-picker.ts
new file mode 100644
index 000000000..6d94427b8
--- /dev/null
+++ b/packages/flow-core/src/components/f-color-picker/f-color-picker.ts
@@ -0,0 +1,197 @@
+import { html, PropertyValueMap, unsafeCSS } from "lit";
+import { property, query, state } from "lit/decorators.js";
+import globalStyle from "./f-color-picker-global.scss?inline";
+import eleStyle from "./f-color-picker.scss?inline";
+import { FRoot } from "../../mixins/components/f-root/f-root";
+import { flowElement } from "./../../utils";
+import { injectCss } from "@cldcvr/flow-core-config";
+
+import "vanilla-colorful";
+import { FPopover } from "../f-popover/f-popover";
+import { FDiv } from "../f-div/f-div";
+import { FInput } from "../f-input/f-input";
+import { classMap } from "lit-html/directives/class-map.js";
+
+injectCss("f-color-picker", globalStyle);
+
+export type FColorPickerState = "primary" | "default" | "success" | "warning" | "danger";
+export type FColorPickerInputEvent = {
+ value?: string;
+};
+
+@flowElement("f-color-picker")
+export class FColorPicker extends FRoot {
+ /**
+ * css loaded from scss file
+ */
+ static styles = [
+ unsafeCSS(eleStyle),
+ unsafeCSS(globalStyle),
+ ...FPopover.styles,
+ ...FDiv.styles,
+ ...FInput.styles
+ ];
+
+ /**
+ * @attribute Variants are various visual representations of an input field.
+ */
+ @property({ reflect: true, type: String })
+ variant?: "curved" | "round" | "block" = "curved";
+
+ /**
+ * @attribute States are used to communicate purpose and connotations.
+ */
+ @property({ reflect: true, type: String })
+ state?: FColorPickerState = "default";
+
+ /**
+ * @attribute f-input can have 2 sizes. By default size is inherited by the parent f-field.
+ */
+ @property({ reflect: true, type: String })
+ size?: "medium" | "small" = "medium";
+
+ /**
+ * @attribute Defines the value of an f-input. Validation rules are applied on the value depending on the type property of the f-text-input.
+ */
+ @property({ reflect: true, type: String })
+ value?: string;
+
+ /**
+ * @attribute Shows disabled state of input element
+ */
+ @property({ reflect: true, type: Boolean })
+ disabled?: boolean = false;
+
+ /**
+ * @attribute When true the user can not select the input element.
+ */
+ @property({ reflect: true, type: Boolean, attribute: "read-only" })
+ readOnly?: boolean = false;
+
+ @query("#f-color-picker-popover")
+ popoverElement!: FPopover;
+
+ @state()
+ isOpen = false;
+
+ @query("#f-color-picker-input")
+ inputElement!: FDiv;
+
+ defaultHexColor: string = "#000000";
+
+ handleColorChange(event: CustomEvent<{ value: string }>) {
+ this.value = event.detail.value;
+ this.dispatchInputEvent(this.value);
+ }
+ handleFocus(_event: FocusEvent) {
+ if (!this.readOnly) {
+ this.isOpen = true;
+ }
+ }
+ handleOverlayClick() {
+ this.isOpen = false;
+ }
+
+ handleHexInput(event: CustomEvent) {
+ this.value = event.detail.value;
+ this.dispatchInputEvent(this.value);
+ }
+ handleKeydown(event: KeyboardEvent) {
+ if (event.key === "Enter") {
+ this.isOpen = false;
+ }
+ }
+
+ dispatchInputEvent(value?: string) {
+ const event = new CustomEvent("input", {
+ detail: {
+ value
+ },
+ bubbles: true,
+ composed: true
+ });
+
+ this.dispatchEvent(event);
+ }
+
+ get isValueEmpty() {
+ return (
+ this.value === undefined || this.value === null || (this.value && this.value.trim() === "")
+ );
+ }
+
+ sizeMap = {
+ small: "28px",
+ medium: "36px"
+ };
+ get colorPickerTypeTemplate() {
+ return html`
+
+
+
+ `;
+ }
+
+ render() {
+ const classes = { focused: this.isOpen, "no-color": this.isValueEmpty };
+ // render empty string, since there no need of any child element
+ return html`
+
+
+
+
+
+
+
+
+
+
+ ${this.colorPickerTypeTemplate}
+
+
+ `;
+ }
+ protected updated(changedProperties: PropertyValueMap | Map): void {
+ super.updated(changedProperties);
+ this.popoverElement.target = this.inputElement;
+ }
+}
+
+/**
+ * Required for typescript
+ */
+declare global {
+ interface HTMLElementTagNameMap {
+ "f-color-picker": FColorPicker;
+ }
+}
diff --git a/packages/flow-core/src/components/f-div/f-div.ts b/packages/flow-core/src/components/f-div/f-div.ts
index 924166e51..62fa92c80 100644
--- a/packages/flow-core/src/components/f-div/f-div.ts
+++ b/packages/flow-core/src/components/f-div/f-div.ts
@@ -360,13 +360,15 @@ export class FDiv extends FRoot {
this.checkHighlight();
}
- if (this.variant === "round") {
- this.style.borderRadius = `${this.offsetHeight / 2}px`;
- } else if (this.variant === "curved") {
- this.style.borderRadius = `4px`;
- } else {
- this.style.borderRadius = "0px";
- }
+ void this.updateComplete.then(() => {
+ if (this.variant === "round") {
+ this.style.borderRadius = `${this.offsetHeight / 2}px`;
+ } else if (this.variant === "curved") {
+ this.style.borderRadius = `4px`;
+ } else {
+ this.style.borderRadius = "0px";
+ }
+ });
}
}
diff --git a/packages/flow-core/src/components/f-form-field/f-form-field.ts b/packages/flow-core/src/components/f-form-field/f-form-field.ts
index c2d09952a..f181bddd1 100644
--- a/packages/flow-core/src/components/f-form-field/f-form-field.ts
+++ b/packages/flow-core/src/components/f-form-field/f-form-field.ts
@@ -33,14 +33,7 @@ export class FFormField extends FRoot {
-
+
diff --git a/packages/flow-core/src/index.ts b/packages/flow-core/src/index.ts
index a416c71b0..abfc8ab2c 100644
--- a/packages/flow-core/src/index.ts
+++ b/packages/flow-core/src/index.ts
@@ -47,6 +47,7 @@ export * from "./components/f-document-viewer/f-document-viewer";
export * from "./mixins/components/f-root/f-root";
export * from "./components/f-form-field/f-form-field";
export * from "./components/f-input/f-input-light";
+export * from "./components/f-color-picker/f-color-picker";
export { html } from "lit";
diff --git a/packages/flow-core/vite.config.ts b/packages/flow-core/vite.config.ts
index bcb704ca1..175727bee 100644
--- a/packages/flow-core/vite.config.ts
+++ b/packages/flow-core/vite.config.ts
@@ -16,7 +16,14 @@ export default defineConfig({
// If we want to publish standalone components we don't externalize lit,
// if you are going to use lit in your own project, you can make it a dep instead.
// external: /^lit/, <-- comment this line
- external: ["axios", "emoji-mart", /^lit/, "rxjs", "@cldcvr/flow-core-config"]
+ external: [
+ "axios",
+ "emoji-mart",
+ /^lit/,
+ "rxjs",
+ "@cldcvr/flow-core-config",
+ "vanilla-colorful"
+ ]
}
}
});
diff --git a/packages/flow-form-builder/CHANGELOG.md b/packages/flow-form-builder/CHANGELOG.md
index 9e6ad2725..9d251f307 100644
--- a/packages/flow-form-builder/CHANGELOG.md
+++ b/packages/flow-form-builder/CHANGELOG.md
@@ -2,6 +2,12 @@
# Change Log
+## [2.2.0] - 2023-12-04
+
+### Minor Changes
+
+- `f-color-picker` field integrated
+
## [2.1.0] - 2023-11-27
### Minor Changes
diff --git a/packages/flow-form-builder/package.json b/packages/flow-form-builder/package.json
index cf1809ca1..3dfc44228 100644
--- a/packages/flow-form-builder/package.json
+++ b/packages/flow-form-builder/package.json
@@ -1,6 +1,6 @@
{
"name": "@cldcvr/flow-form-builder",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Form builder for the flow design system",
"module": "dist/flow-form-builder.es.js",
"main": "dist/flow-form-builder.cjs.js",
diff --git a/packages/flow-form-builder/src/components/f-form-builder/fields/color.ts b/packages/flow-form-builder/src/components/f-form-builder/fields/color.ts
new file mode 100644
index 000000000..19223449d
--- /dev/null
+++ b/packages/flow-form-builder/src/components/f-form-builder/fields/color.ts
@@ -0,0 +1,35 @@
+import { html } from "lit";
+import { FFormInputElements, FormBuilderColorPickerField, FormBuilderField } from "../../../types";
+import { Ref, ref } from "lit/directives/ref.js";
+import { ifDefined } from "lit-html/directives/if-defined.js";
+import { getSlots } from "../../../modules/helpers";
+export default function (
+ name: string,
+ _field: FormBuilderField,
+ fieldRef: Ref,
+ value: unknown
+) {
+ const field = _field as FormBuilderColorPickerField;
+ return html`
+
+ ${getSlots(name, field)}
+
+ `;
+}
diff --git a/packages/flow-form-builder/src/components/f-form-builder/fields/index.ts b/packages/flow-form-builder/src/components/f-form-builder/fields/index.ts
index ccd3c18b8..037ee9fc2 100644
--- a/packages/flow-form-builder/src/components/f-form-builder/fields/index.ts
+++ b/packages/flow-form-builder/src/components/f-form-builder/fields/index.ts
@@ -15,6 +15,7 @@ import separator from "./separator";
import emoji from "./emoji";
import hidden from "./hidden";
import datetime from "./datetime";
+import color from "./color";
const all: Record = {
text,
@@ -37,7 +38,8 @@ const all: Record = {
separator,
emoji,
hidden,
- datetime
+ datetime,
+ color
};
export default all;
diff --git a/packages/flow-form-builder/src/modules/helpers.ts b/packages/flow-form-builder/src/modules/helpers.ts
index 6e4be987d..f30557b9b 100644
--- a/packages/flow-form-builder/src/modules/helpers.ts
+++ b/packages/flow-form-builder/src/modules/helpers.ts
@@ -27,7 +27,8 @@ import {
FSelect,
FSuggest,
FSwitch,
- FTextArea
+ FTextArea,
+ FColorPicker
} from "@cldcvr/flow-core";
import checkboxGroupGlobalStyles from "./../components/f-checkbox-group/f-checkbox-group-global.scss?inline";
@@ -167,9 +168,10 @@ export function getEssentialFlowCoreStyles(): CSSResult[] {
...FSuggest.styles,
...FSwitch.styles,
...FTextArea.styles,
+ ...FColorPicker.styles,
unsafeCSS(checkboxGroupGlobalStyles),
unsafeCSS(radioGroupGlobalStyles),
unsafeCSS(fieldSeparatorGlobalStyles),
unsafeCSS(formObjectGlobalStyles)
- ];
+ ] as CSSResult[];
}
diff --git a/packages/flow-form-builder/src/types.ts b/packages/flow-form-builder/src/types.ts
index eeb90c6d4..fb147bff9 100644
--- a/packages/flow-form-builder/src/types.ts
+++ b/packages/flow-form-builder/src/types.ts
@@ -19,7 +19,8 @@ import {
FDateTimePickerState,
FDateOption,
DateDisableType,
- FRootTooltip
+ FRootTooltip,
+ FColorPickerState
} from "@cldcvr/flow-core";
import { BetweenParams } from "./modules/validation/rules/between";
import { Subject } from "rxjs";
@@ -56,6 +57,11 @@ export type FormBuilderArrayField = FormBuilderBaseField & {
label: FormBuilderLabel;
allowEmpty?: boolean;
};
+export type FormBuilderColorPickerField = FormBuilderBaseField & {
+ type: "color";
+ state?: FColorPickerState;
+ readOnly?: boolean;
+};
export type FormBuilderSeparatorField = FormBuilderFieldEvents & {
id?: string; // id to uniquely identify in DOM
state?: FDividerState;
@@ -267,7 +273,8 @@ export type FormBuilderField =
| FormBuilderSeparatorField
| FormBuilderEmojiField
| FormBuilderHiddenField
- | FormBuilderDatetimeField; // add other field types
+ | FormBuilderDatetimeField
+ | FormBuilderColorPickerField; // add other field types
export type FormBuilderShowCondition = (value: T) => boolean;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f5ef4d865..5776b29b3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -263,6 +263,9 @@ importers:
validate-color:
specifier: ^2.2.1
version: 2.2.4
+ vanilla-colorful:
+ specifier: ^0.7.2
+ version: 0.7.2
devDependencies:
'@custom-elements-manifest/analyzer':
specifier: ^0.8.1
@@ -14630,6 +14633,10 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
+ /vanilla-colorful@0.7.2:
+ resolution: {integrity: sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==}
+ dev: false
+
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
diff --git a/stories/flow-core/f-color-picker.stories.ts b/stories/flow-core/f-color-picker.stories.ts
new file mode 100644
index 000000000..691eff628
--- /dev/null
+++ b/stories/flow-core/f-color-picker.stories.ts
@@ -0,0 +1,169 @@
+import { html } from "lit-html";
+
+export default {
+ title: "@cldcvr/flow-core/f-color-picker",
+
+ parameters: {
+ controls: {
+ hideNoControlsWarning: true
+ }
+ }
+};
+
+export const Playground = {
+ render: (args: Record) => {
+ return html``;
+ },
+
+ name: "Playground",
+
+ parameters: {
+ docs: {
+ inlineStories: false,
+ iframeHeight: 200
+ }
+ },
+
+ argTypes: {
+ variant: {
+ control: "select",
+ options: ["curved", "round", "block"]
+ },
+ state: {
+ control: "select",
+ options: ["default", "success", "warning", "danger", "primary"]
+ },
+ size: {
+ control: "select",
+ options: ["medium", "small"]
+ },
+ value: {
+ control: "text"
+ },
+ disabled: {
+ control: "boolean"
+ },
+ ["read-only"]: {
+ control: "boolean"
+ }
+ },
+
+ args: {
+ variant: "curved",
+ state: "default",
+ size: "medium",
+ value: "#000",
+ disabled: false,
+ ["read-only"]: false
+ }
+};
+
+export const Variant = {
+ render: () => {
+ const variants = ["curved", "round", "block"];
+
+ return html`
+
+ ${variants.map(
+ item =>
+ html`
+ Label (variant="${item}")
+ This is a Subtext (Helper Text)
+ Optional
+
+ `
+ )}
+
+ `;
+ },
+
+ name: "variant"
+};
+
+export const Size = {
+ render: () => {
+ const sizes = ["small", "medium"];
+
+ return html`
+
+ ${sizes.map(
+ item =>
+ html`
+ Label (size="${item}")
+ This is a Subtext (Helper Text)
+ `
+ )}
+
+ `;
+ },
+
+ name: "size"
+};
+
+export const State = {
+ render: () => {
+ const states = [
+ ["default", "primary", "success"],
+ ["danger", "warning"]
+ ];
+
+ return html`
+
+ ${states.map(
+ item =>
+ html`
+ ${item.map(
+ state =>
+ html`
+ Label (state="${state}")
+ This is a Subtext (Helper Text)
+ `
+ )}
+ `
+ )}
+
+ `;
+ },
+
+ name: "state"
+};
+
+export const Flags = {
+ render: () => {
+ const flagNames: Record = {
+ 0: "Disabled",
+ 1: "Readonly"
+ };
+
+ return html`
+
+ ${[0, 1].map(
+ item =>
+ html`
+ ${flagNames[item]}
+ This is a Subtext (Helper Text)
+ `
+ )}
+
+ `;
+ },
+
+ name: "Flags"
+};
diff --git a/stories/flow-form-builder/f-formbuilder-field.ts b/stories/flow-form-builder/f-formbuilder-field.ts
index d3eb4ceca..ffcb496ea 100644
--- a/stories/flow-form-builder/f-formbuilder-field.ts
+++ b/stories/flow-form-builder/f-formbuilder-field.ts
@@ -13,16 +13,27 @@ const field: FormBuilderField = {
description: "following fields are used for demo purpose only"
},
fields: {
- number: {
- type: "number",
- validationRules: [
- {
- name: "max-value",
- params: {
- max: 5
+ paymentInfo: {
+ type: "object",
+ fields: {
+ cardNumber: {
+ type: "number",
+ validationRules: [
+ {
+ name: "max-value",
+ params: {
+ max: 5
+ }
+ }
+ ]
+ },
+ color: {
+ type: "color",
+ label: {
+ title: "Color"
}
}
- ]
+ }
},
hiddenField: {
type: "hidden",
@@ -62,6 +73,7 @@ const field: FormBuilderField = {
state: "success",
suffix: "suggested"
},
+
logo: {
label: { title: "Logo" },
type: "emoji",