diff --git a/packages/ui-library/src/components/checkbox/index.ts b/packages/ui-library/src/components/checkbox/index.ts
index 3fc5d01bb..29ce0abea 100644
--- a/packages/ui-library/src/components/checkbox/index.ts
+++ b/packages/ui-library/src/components/checkbox/index.ts
@@ -78,6 +78,21 @@ export class BlrCheckbox extends LitElement {
}
}
+ connectedCallback() {
+ super.connectedCallback();
+ addEventListener('propChanged', this._onPropChanged);
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ removeEventListener('propChanged', this._onPropChanged);
+ }
+
+ _onPropChanged = (event: any) => {
+ this.hasError = event.detail.hasError;
+ this.errorMessage = event.detail.errorMessage;
+ };
+
protected handleChange(event: Event) {
if (!this.disabled && !this.readonly) {
this.currentIndeterminateState = false;
diff --git a/packages/ui-library/src/components/form-example-with-slot/index.css.ts b/packages/ui-library/src/components/form-example-with-slot/index.css.ts
new file mode 100644
index 000000000..8c6f11043
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-with-slot/index.css.ts
@@ -0,0 +1,76 @@
+import { typeSafeNestedCss as css } from "../../utils/nested-typesafe-css-literals";
+import { renderThemedCssStrings } from "../../foundation/_tokens-generated/index.pseudo.generated";
+
+export const { tokenizedLight: captionLight, tokenizedDark: captionDark } = renderThemedCssStrings((componentTokens) => {
+ const { CaptionComponent } = componentTokens.Forms;
+
+ return css`
+ .blr-form-caption {
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+ color: ${CaptionComponent.Text.TextColor.Hint};
+
+ &.error {
+ color: ${CaptionComponent.Text.TextColor.Error};
+ }
+
+ &.sm {
+ padding: ${CaptionComponent.Container.Padding.SM};
+ gap: ${CaptionComponent.Container.ItemSpacing.SM};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.SM};
+ height: ${CaptionComponent.Icon.IconSize.SM};
+ width: ${CaptionComponent.Icon.IconSize.SM};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.SM};
+ font-family: ${CaptionComponent.Text.Typography.SM.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.SM.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.SM.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.SM.lineHeight};
+ }
+ }
+
+ &.md {
+ padding: ${CaptionComponent.Container.Padding.MD};
+ gap: ${CaptionComponent.Container.ItemSpacing.MD};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.MD};
+ height: ${CaptionComponent.Icon.IconSize.MD};
+ width: ${CaptionComponent.Icon.IconSize.MD};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.MD};
+ font-family: ${CaptionComponent.Text.Typography.MD.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.MD.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.MD.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.MD.lineHeight};
+ }
+ }
+
+ &.lg {
+ padding: ${CaptionComponent.Container.Padding.LG};
+ gap: ${CaptionComponent.Container.ItemSpacing.LG};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.LG};
+ height: ${CaptionComponent.Icon.IconSize.LG};
+ width: ${CaptionComponent.Icon.IconSize.LG};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.LG};
+ font-family: ${CaptionComponent.Text.Typography.LG.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.LG.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.LG.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.LG.lineHeight};
+ }
+ }
+ }
+ `;
+});
diff --git a/packages/ui-library/src/components/form-example-with-slot/index.stories.ts b/packages/ui-library/src/components/form-example-with-slot/index.stories.ts
new file mode 100644
index 000000000..ab169a877
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-with-slot/index.stories.ts
@@ -0,0 +1,50 @@
+/* eslint-disable no-console */
+import { PureIconKeys } from '@boiler/icons';
+import { FormSizes, CaptionVariants } from '../../globals/constants';
+import { BlrFormExampleWithSlotType } from './index';
+import { BlrFormExampleWithSlotRenderFunction } from './renderFunction';
+import { Themes } from '../../foundation/_tokens-generated/index.themes';
+import { html } from 'lit-html';
+import '../../index';
+
+const sharedStyles = html`
+
+`;
+
+export default {
+ title: 'Components/Form Example',
+ argTypes: {},
+ parameters: {
+ badges: ['Draft'],
+ design: {
+ type: 'figma',
+ url: 'https://www.figma.com/file/C4vgEKz8mKyulJ4gm3Qdql/%F0%9F%AB%A7-%5BBLR%5D-The-B01LER?node-id=3618%3A125223&mode=dev',
+ },
+ viewMode: 'docs',
+ layout: 'centered',
+ docs: {
+ description: {
+ component: `
+ This is experimental form.
+
+ `,
+ },
+ },
+ },
+};
+
+export const BlrFormExampleWithSlot = (params: BlrFormExampleWithSlotType) =>
+ BlrFormExampleWithSlotRenderFunction(params);
+
+BlrFormExampleWithSlot.storyName = 'BlrFormExampleWithSlot';
+
+const args: BlrFormExampleWithSlotType = {
+ theme: 'Light',
+};
+
+BlrFormExampleWithSlot.args = args;
diff --git a/packages/ui-library/src/components/form-example-with-slot/index.ts b/packages/ui-library/src/components/form-example-with-slot/index.ts
new file mode 100644
index 000000000..ab2f7793b
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-with-slot/index.ts
@@ -0,0 +1,144 @@
+import { LitElement, html } from 'lit';
+import { TAG_NAME } from './renderFunction';
+import { ThemeType } from '../../foundation/_tokens-generated/index.themes';
+import { property } from 'lit/decorators.js';
+
+export class BlrFormExampleWithSlot extends LitElement {
+ @property() theme: ThemeType = 'Light';
+ @property() firstInputValue: string = '';
+ @property() secondInputValue: string = '';
+ @property({ reflect: true }) checkBoxChecked: boolean = false;
+
+ protected render() {
+ return html`
+
+
+
+
+
+ `;
+ }
+
+ private handleSubmit(e) {
+ const slot = this.renderRoot?.querySelector('slot');
+ const assignedNodes = slot?.assignedElements({ flatten: true }) ?? [];
+ assignedNodes.forEach((node: any) => {
+ if (node.name === 'firstInput') {
+ if (node.hasAttribute('required') && node.value === '') {
+ node._onPropChanged({
+ detail: {
+ hasError: true,
+ errorMessage: 'This is a required field',
+ },
+ });
+ } else {
+ this.firstInputValue = node.value;
+ }
+ }
+
+ if (node.name === 'secondInput') {
+ if (node.hasAttribute('required') && node.value === '') {
+ node._onPropChanged({
+ detail: {
+ hasError: true,
+ errorMessage: 'This is a second required field',
+ },
+ });
+ } else {
+ this.secondInputValue = node.value;
+ }
+ }
+
+ if (node.name === 'checkInput' && !node.hasAttribute('checked')) {
+ node._onPropChanged({
+ detail: {
+ hasError: true,
+ errorMessage: 'Error: Unchecked',
+ },
+ });
+ }
+ });
+
+ // just to simulate the value change. Remove later
+ setTimeout(() => {
+ this.dispatchEvent(
+ new CustomEvent('propChanged', {
+ detail: { hasError: false, errorMessage: '' },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }, 3000);
+
+ console.log(
+ `The submitted value are firstName: ${this.firstInputValue} lastName: ${this.secondInputValue} and TOA checked is ${this.checkBoxChecked}`
+ );
+ }
+
+ private handleFirstInputChange(evt: any) {
+ this.firstInputValue = evt.detail.originalEvent.target.value;
+ }
+
+ private handleSecondInputChange(evt: any) {
+ this.secondInputValue = evt.detail.originalEvent.target.value;
+ }
+
+ private handleCheckInput(evt: any) {
+ this.checkBoxChecked = evt.detail.checkedState;
+ }
+}
+
+if (!customElements.get(TAG_NAME)) {
+ customElements.define(TAG_NAME, BlrFormExampleWithSlot);
+}
+
+export type BlrFormExampleWithSlotType = Omit;
diff --git a/packages/ui-library/src/components/form-example-with-slot/renderFunction.ts b/packages/ui-library/src/components/form-example-with-slot/renderFunction.ts
new file mode 100644
index 000000000..cc69ecb1e
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-with-slot/renderFunction.ts
@@ -0,0 +1,7 @@
+import { BlrFormExampleWithSlotType } from '.';
+import { genericBlrComponentRenderer } from '../../utils/typesafe-generic-component-renderer';
+
+export const TAG_NAME = 'blr-form-example-with-slot';
+
+export const BlrFormExampleWithSlotRenderFunction = (params: BlrFormExampleWithSlotType) =>
+ genericBlrComponentRenderer(TAG_NAME, { ...params });
diff --git a/packages/ui-library/src/components/form-example-without-slot/index.css.ts b/packages/ui-library/src/components/form-example-without-slot/index.css.ts
new file mode 100644
index 000000000..8c6f11043
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-without-slot/index.css.ts
@@ -0,0 +1,76 @@
+import { typeSafeNestedCss as css } from "../../utils/nested-typesafe-css-literals";
+import { renderThemedCssStrings } from "../../foundation/_tokens-generated/index.pseudo.generated";
+
+export const { tokenizedLight: captionLight, tokenizedDark: captionDark } = renderThemedCssStrings((componentTokens) => {
+ const { CaptionComponent } = componentTokens.Forms;
+
+ return css`
+ .blr-form-caption {
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+ color: ${CaptionComponent.Text.TextColor.Hint};
+
+ &.error {
+ color: ${CaptionComponent.Text.TextColor.Error};
+ }
+
+ &.sm {
+ padding: ${CaptionComponent.Container.Padding.SM};
+ gap: ${CaptionComponent.Container.ItemSpacing.SM};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.SM};
+ height: ${CaptionComponent.Icon.IconSize.SM};
+ width: ${CaptionComponent.Icon.IconSize.SM};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.SM};
+ font-family: ${CaptionComponent.Text.Typography.SM.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.SM.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.SM.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.SM.lineHeight};
+ }
+ }
+
+ &.md {
+ padding: ${CaptionComponent.Container.Padding.MD};
+ gap: ${CaptionComponent.Container.ItemSpacing.MD};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.MD};
+ height: ${CaptionComponent.Icon.IconSize.MD};
+ width: ${CaptionComponent.Icon.IconSize.MD};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.MD};
+ font-family: ${CaptionComponent.Text.Typography.MD.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.MD.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.MD.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.MD.lineHeight};
+ }
+ }
+
+ &.lg {
+ padding: ${CaptionComponent.Container.Padding.LG};
+ gap: ${CaptionComponent.Container.ItemSpacing.LG};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.LG};
+ height: ${CaptionComponent.Icon.IconSize.LG};
+ width: ${CaptionComponent.Icon.IconSize.LG};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.LG};
+ font-family: ${CaptionComponent.Text.Typography.LG.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.LG.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.LG.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.LG.lineHeight};
+ }
+ }
+ }
+ `;
+});
diff --git a/packages/ui-library/src/components/form-example-without-slot/index.stories.ts b/packages/ui-library/src/components/form-example-without-slot/index.stories.ts
new file mode 100644
index 000000000..36b9e28d7
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-without-slot/index.stories.ts
@@ -0,0 +1,50 @@
+/* eslint-disable no-console */
+import { PureIconKeys } from '@boiler/icons';
+import { FormSizes, CaptionVariants } from '../../globals/constants';
+import { BlrFormExampleWithoutSlotType } from './index';
+import { BlrFormExampleWithoutSlotRenderFunction } from './renderFunction';
+import { Themes } from '../../foundation/_tokens-generated/index.themes';
+import { html } from 'lit-html';
+import '../../index';
+
+const sharedStyles = html`
+
+`;
+
+export default {
+ title: 'Components/Form Example',
+ argTypes: {},
+ parameters: {
+ badges: ['Draft'],
+ design: {
+ type: 'figma',
+ url: 'https://www.figma.com/file/C4vgEKz8mKyulJ4gm3Qdql/%F0%9F%AB%A7-%5BBLR%5D-The-B01LER?node-id=3618%3A125223&mode=dev',
+ },
+ viewMode: 'docs',
+ layout: 'centered',
+ docs: {
+ description: {
+ component: `
+ This is experimental form.
+
+ `,
+ },
+ },
+ },
+};
+
+export const BlrFormExampleWithoutSlot = (params: BlrFormExampleWithoutSlotType) =>
+ BlrFormExampleWithoutSlotRenderFunction(params);
+
+BlrFormExampleWithoutSlot.storyName = 'BlrFormExampleWithoutSlot';
+
+const args: BlrFormExampleWithoutSlotType = {
+ theme: 'Light',
+};
+
+BlrFormExampleWithoutSlot.args = args;
diff --git a/packages/ui-library/src/components/form-example-without-slot/index.ts b/packages/ui-library/src/components/form-example-without-slot/index.ts
new file mode 100644
index 000000000..f70d69c94
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-without-slot/index.ts
@@ -0,0 +1,159 @@
+import { LitElement, html } from 'lit';
+import { TAG_NAME } from './renderFunction';
+import { ThemeType } from '../../foundation/_tokens-generated/index.themes';
+import { property, query } from 'lit/decorators.js';
+
+export class BlrFormExampleWithoutSlot extends LitElement {
+ @property() theme: ThemeType = 'Light';
+ @property() firstInputValue: string = '';
+ @property() secondInputValue: string = '';
+ @property() errorMessage: string = '';
+ @property() firstNameHasError?: boolean = false;
+ @property() lastNameHasError?: boolean = false;
+ @property({ reflect: true }) checkBoxChecked: boolean = false;
+
+ @query('blr-text-input[name="firstInput"]') firstInputElement!: HTMLElement;
+ @query('blr-text-input[name="secondInput"]') secondInputElement!: HTMLElement;
+ @query('blr-checkbox[name="checkInput"]') checkboxInputElement!: HTMLElement;
+
+ protected render() {
+ return html`
+
+ `;
+ }
+
+ private handleSubmit(event) {
+ event.preventDefault();
+
+ if (
+ (this.secondInputElement.hasAttribute('required') && this.secondInputValue.trim() === '') ||
+ (this.secondInputElement.hasAttribute('required') && this.secondInputValue.trim() === '') ||
+ !this.checkboxInputElement.hasAttribute('checked')
+ ) {
+ if (this.firstInputElement.hasAttribute('required') && this.firstInputValue.trim() === '') {
+ this.firstInputElement._onPropChanged({
+ detail: {
+ hasError: true,
+ errorMessage: 'This is a required field',
+ },
+ });
+ }
+
+ if (this.secondInputElement.hasAttribute('required') && this.secondInputValue.trim() === '') {
+ this.secondInputElement._onPropChanged({
+ detail: {
+ hasError: true,
+ errorMessage: 'This is a required field',
+ },
+ });
+ }
+
+ if (!this.checkboxInputElement.hasAttribute('checked')) {
+ this.checkboxInputElement._onPropChanged({
+ detail: {
+ hasError: true,
+ errorMessage: 'This is a required field',
+ },
+ });
+ }
+ console.log('Please provide a value for both first input and last input fields and check the checkbox.');
+ // just to simulate the value change. Remove later
+ setTimeout(() => {
+ this.dispatchEvent(
+ new CustomEvent('propChanged', {
+ detail: { hasError: false, errorMessage: '' },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }, 3000);
+ return;
+ }
+
+ console.log(
+ `First Input: ${this.firstInputValue}, Second Input: ${this.secondInputValue}, checkbox checked: ${this.checkBoxChecked}`
+ );
+ }
+
+ handleFirstInputChange(event: CustomEvent) {
+ this.firstInputValue = event.detail.originalEvent.target.value;
+ }
+
+ handleSecondInputChange(event: CustomEvent) {
+ this.secondInputValue = event.detail.originalEvent.target.value;
+ }
+
+ private handleCheckInput(evt: any) {
+ this.checkBoxChecked = evt.detail.checkedState;
+ }
+}
+
+if (!customElements.get(TAG_NAME)) {
+ customElements.define(TAG_NAME, BlrFormExampleWithoutSlot);
+}
+
+export type BlrFormExampleWithoutSlotType = Omit;
diff --git a/packages/ui-library/src/components/form-example-without-slot/renderFunction.ts b/packages/ui-library/src/components/form-example-without-slot/renderFunction.ts
new file mode 100644
index 000000000..89dd9a81f
--- /dev/null
+++ b/packages/ui-library/src/components/form-example-without-slot/renderFunction.ts
@@ -0,0 +1,7 @@
+import { BlrFormExampleWithoutSlotType } from '.';
+import { genericBlrComponentRenderer } from '../../utils/typesafe-generic-component-renderer';
+
+export const TAG_NAME = 'blr-form-example-without-slot';
+
+export const BlrFormExampleWithoutSlotRenderFunction = (params: BlrFormExampleWithoutSlotType) =>
+ genericBlrComponentRenderer(TAG_NAME, { ...params });
diff --git a/packages/ui-library/src/components/form/index.css.ts b/packages/ui-library/src/components/form/index.css.ts
new file mode 100644
index 000000000..8c6f11043
--- /dev/null
+++ b/packages/ui-library/src/components/form/index.css.ts
@@ -0,0 +1,76 @@
+import { typeSafeNestedCss as css } from "../../utils/nested-typesafe-css-literals";
+import { renderThemedCssStrings } from "../../foundation/_tokens-generated/index.pseudo.generated";
+
+export const { tokenizedLight: captionLight, tokenizedDark: captionDark } = renderThemedCssStrings((componentTokens) => {
+ const { CaptionComponent } = componentTokens.Forms;
+
+ return css`
+ .blr-form-caption {
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+ color: ${CaptionComponent.Text.TextColor.Hint};
+
+ &.error {
+ color: ${CaptionComponent.Text.TextColor.Error};
+ }
+
+ &.sm {
+ padding: ${CaptionComponent.Container.Padding.SM};
+ gap: ${CaptionComponent.Container.ItemSpacing.SM};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.SM};
+ height: ${CaptionComponent.Icon.IconSize.SM};
+ width: ${CaptionComponent.Icon.IconSize.SM};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.SM};
+ font-family: ${CaptionComponent.Text.Typography.SM.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.SM.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.SM.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.SM.lineHeight};
+ }
+ }
+
+ &.md {
+ padding: ${CaptionComponent.Container.Padding.MD};
+ gap: ${CaptionComponent.Container.ItemSpacing.MD};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.MD};
+ height: ${CaptionComponent.Icon.IconSize.MD};
+ width: ${CaptionComponent.Icon.IconSize.MD};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.MD};
+ font-family: ${CaptionComponent.Text.Typography.MD.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.MD.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.MD.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.MD.lineHeight};
+ }
+ }
+
+ &.lg {
+ padding: ${CaptionComponent.Container.Padding.LG};
+ gap: ${CaptionComponent.Container.ItemSpacing.LG};
+
+ .blr-icon {
+ padding-top: ${CaptionComponent.IconWrapper.PaddingTop.LG};
+ height: ${CaptionComponent.Icon.IconSize.LG};
+ width: ${CaptionComponent.Icon.IconSize.LG};
+ }
+
+ .blr-caption-text {
+ padding: ${CaptionComponent.TextWrapper.Padding.LG};
+ font-family: ${CaptionComponent.Text.Typography.LG.fontFamily}, sans-serif;
+ font-weight: ${CaptionComponent.Text.Typography.LG.fontWeight};
+ font-size: ${CaptionComponent.Text.Typography.LG.fontSize};
+ line-height: ${CaptionComponent.Text.Typography.LG.lineHeight};
+ }
+ }
+ }
+ `;
+});
diff --git a/packages/ui-library/src/components/form/index.stories.ts b/packages/ui-library/src/components/form/index.stories.ts
new file mode 100644
index 000000000..1389756c2
--- /dev/null
+++ b/packages/ui-library/src/components/form/index.stories.ts
@@ -0,0 +1,49 @@
+/* eslint-disable no-console */
+import { PureIconKeys } from '@boiler/icons';
+import { FormSizes, CaptionVariants } from '../../globals/constants';
+import { BlrFormType } from './index';
+import { BlrFormRenderFunction } from './renderFunction';
+import { Themes } from '../../foundation/_tokens-generated/index.themes';
+import { html } from 'lit-html';
+import '../../index';
+
+const sharedStyles = html`
+
+`;
+
+export default {
+ title: 'Components/Form',
+ argTypes: {},
+ parameters: {
+ badges: ['Draft'],
+ design: {
+ type: 'figma',
+ url: 'https://www.figma.com/file/C4vgEKz8mKyulJ4gm3Qdql/%F0%9F%AB%A7-%5BBLR%5D-The-B01LER?node-id=3618%3A125223&mode=dev',
+ },
+ viewMode: 'docs',
+ layout: 'centered',
+ docs: {
+ description: {
+ component: `
+ This is experimental form.
+
+ `,
+ },
+ },
+ },
+};
+
+export const BlrForm = (params: BlrFormType) => BlrFormRenderFunction(params);
+
+BlrForm.storyName = 'Form';
+
+const args: BlrFormType = {
+ theme: 'Light',
+};
+
+BlrForm.args = args;
diff --git a/packages/ui-library/src/components/form/index.ts b/packages/ui-library/src/components/form/index.ts
new file mode 100644
index 000000000..a9ce55017
--- /dev/null
+++ b/packages/ui-library/src/components/form/index.ts
@@ -0,0 +1,30 @@
+import { LitElement, html } from 'lit';
+import { TAG_NAME } from './renderFunction';
+import { property } from 'lit/decorators.js';
+
+export class BlrForm extends LitElement {
+ @property({ type: Function }) handleSubmit = () => {};
+
+ protected render() {
+ return html``;
+ }
+}
+
+if (!customElements.get(TAG_NAME)) {
+ customElements.define(TAG_NAME, BlrForm);
+}
+
+export type BlrFormType = Omit;
diff --git a/packages/ui-library/src/components/form/renderFunction.ts b/packages/ui-library/src/components/form/renderFunction.ts
new file mode 100644
index 000000000..96e06f58f
--- /dev/null
+++ b/packages/ui-library/src/components/form/renderFunction.ts
@@ -0,0 +1,7 @@
+import { BlrFormType } from '.';
+import { genericBlrComponentRenderer } from '../../utils/typesafe-generic-component-renderer';
+
+export const TAG_NAME = 'blr-form';
+
+export const BlrFormRenderFunction = (params: BlrFormType) =>
+ genericBlrComponentRenderer(TAG_NAME, { ...params });
diff --git a/packages/ui-library/src/components/text-input/index.ts b/packages/ui-library/src/components/text-input/index.ts
index aeb2f67a7..59be8192a 100644
--- a/packages/ui-library/src/components/text-input/index.ts
+++ b/packages/ui-library/src/components/text-input/index.ts
@@ -69,6 +69,21 @@ export class BlrTextInput extends LitElement {
@state() protected currentType: InputTypes = this.type;
@state() protected isFocused = false;
+ connectedCallback() {
+ super.connectedCallback();
+ addEventListener('propChanged', this._onPropChanged);
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ removeEventListener('propChanged', this._onPropChanged);
+ }
+
+ _onPropChanged = (event: any) => {
+ this.hasError = event.detail.hasError;
+ this.errorMessage = event.detail.errorMessage;
+ };
+
protected togglePassword = () => {
this.currentType = this.currentType === 'password' ? 'text' : 'password';
};
@@ -197,7 +212,6 @@ export class BlrTextInput extends LitElement {
@focus=${this.handleFocus}
maxlength="${this.maxLength}"
pattern="${this.pattern}"
- hasError="${this.hasError}"
@select=${this.handleSelect}
/>
diff --git a/packages/ui-library/src/index.ts b/packages/ui-library/src/index.ts
index f1fd6592f..971ad5b00 100644
--- a/packages/ui-library/src/index.ts
+++ b/packages/ui-library/src/index.ts
@@ -35,6 +35,12 @@ export { BlrTextarea } from './components/textarea';
export { BlrToggleSwitch } from './components/toggle-switch';
+export { BlrForm } from './components/form';
+
+export { BlrFormExampleWithSlot } from './components/form-example-with-slot';
+
+export { BlrFormExampleWithoutSlot } from './components/form-example-without-slot';
+
// Internal
export { BlrCounter } from './components/counter';