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';