Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
FLOW-970 f-color-picker component (#203)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
vikas-cldcvr authored Dec 4, 2023
1 parent ecda224 commit c67565c
Show file tree
Hide file tree
Showing 19 changed files with 551 additions and 32 deletions.
6 changes: 6 additions & 0 deletions packages/flow-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions packages/flow-core/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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%;
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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`<f-color-picker></f-color-picker>`);

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`<f-color-picker value="#CCCCCC"></f-color-picker>`);

const input = el.shadowRoot?.querySelector<FInput>("#hex-input");
expect(input.value).to.equal("#CCCCCC");
});
});
197 changes: 197 additions & 0 deletions packages/flow-core/src/components/f-color-picker/f-color-picker.ts
Original file line number Diff line number Diff line change
@@ -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<FColorPickerInputEvent>("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`<hex-color-picker
style="width:100%;"
.color=${this.value ?? this.defaultHexColor}
@color-changed=${this.handleColorChange}
></hex-color-picker>
<f-div gap="small">
<f-input
id="hex-input"
prefix="HEX"
@input=${this.handleHexInput}
size="small"
.clear=${false}
.value=${this.value ?? this.defaultHexColor}
>
</f-input>
</f-div>`;
}

render() {
const classes = { focused: this.isOpen, "no-color": this.isValueEmpty };
// render empty string, since there no need of any child element
return html`<f-div direction="column" gap="x-small" width="hug-content">
<f-form-field>
<f-div
.width=${this.sizeMap[this.size ?? "medium"]}
.height=${this.sizeMap[this.size ?? "medium"]}
state="custom,${this.value ?? this.defaultHexColor}"
border-state="${this.state}"
.variant=${this.variant}
.disabled=${this.disabled}
?read-only=${this.readOnly}
class=${classMap(classes)}
@click=${this.handleFocus}
id="f-color-picker-input"
data-qa-element=${this.getAttribute("data-qa-element-id")}
></f-div>
<f-div slot="label"><slot name="label"></slot></f-div>
<f-div slot="description"><slot name="description"></slot></f-div>
<f-div slot="icon-tooltip"><slot name="icon-tooltip"></slot></f-div>
<f-div slot="help"><slot name="help"></slot></f-div>
</f-form-field>
<f-popover
id="f-color-picker-popover"
@overlay-click=${this.handleOverlayClick}
@keydown=${this.handleKeydown}
.overlay=${false}
.open=${this.isOpen}
size="small"
>
<f-div direction="column" state="secondary" padding="large" gap="medium" variant="curved">
${this.colorPickerTypeTemplate}
</f-div>
</f-popover>
</f-div>`;
}
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.updated(changedProperties);
this.popoverElement.target = this.inputElement;
}
}

/**
* Required for typescript
*/
declare global {
interface HTMLElementTagNameMap {
"f-color-picker": FColorPicker;
}
}
16 changes: 9 additions & 7 deletions packages/flow-core/src/components/f-div/f-div.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,7 @@ export class FFormField extends FRoot {
<slot name="description"></slot>
</f-div>
</f-div>
<f-div
gap="x-small"
direction="row"
width="100%"
class="f-input-row"
align="middle-center"
overflow="hidden"
>
<f-div gap="x-small" direction="row" width="100%" class="f-input-row" overflow="hidden">
<slot></slot>
</f-div>
<f-div>
Expand Down
1 change: 1 addition & 0 deletions packages/flow-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
9 changes: 8 additions & 1 deletion packages/flow-core/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
});
6 changes: 6 additions & 0 deletions packages/flow-form-builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/flow-form-builder/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading

0 comments on commit c67565c

Please sign in to comment.