diff --git a/package-lock.json b/package-lock.json index 5ef445d..2c7a3be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cardbuilder", - "version": "0.0.16", + "version": "0.0.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cardbuilder", - "version": "0.0.16", + "version": "0.0.19", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.11", diff --git a/src/card-section/card-section.ts b/src/card-section/card-section.ts index b5f9fc7..f4e1671 100644 --- a/src/card-section/card-section.ts +++ b/src/card-section/card-section.ts @@ -7,6 +7,7 @@ import { DecoratedTextWidget } from '../widgets/decorated-text/decorated-text.wi import { DividerWidget } from '../widgets/divider/divider.widget'; import { SelectionInputWidget } from '../widgets/selection-input/selection-input.widget'; import { TextParagraphWidget } from '../widgets/text-paragraph/text-paragraph.widget'; +import { TextInputWidget } from '../widgets/text-input/text-input.widget'; export type CardSectionProps = { header?: string; @@ -18,6 +19,7 @@ export type CardSectionProps = { const widgetNameMap = new Map([ [SelectionInputWidget.name, 'selectionInput'], [TextParagraphWidget.name, 'textParagraph'], + [TextInputWidget.name, 'textInput'], [DecoratedTextWidget.name, 'decoratedText'], [ButtonListWidget.name, 'buttonList'], [DateTimePickerWidget.name, 'dateTimePicker'], diff --git a/src/card-service.ts b/src/card-service.ts index 177c970..fba6a0b 100644 --- a/src/card-service.ts +++ b/src/card-service.ts @@ -11,6 +11,7 @@ import { DecoratedTextBuilder } from './widgets/decorated-text/decorated-text.bu import { DividerBuilder } from './widgets/divider/divider.builder'; import { SelectionInputBuilder } from './widgets/selection-input/selection-input.builder'; import { TextParagraphBuilder } from './widgets/text-paragraph/text-paragraph.builder'; +import { TextInputBuilder } from './widgets/text-input/text-input.builder'; import { Color } from './shared/color'; import { HeaderBuilder } from './header/header.builder'; @@ -95,6 +96,10 @@ export class CardService { public static newDivider(): DividerBuilder { return new DividerBuilder(); } + + public static newTextInput(): TextInputBuilder { + return new TextInputBuilder(); + } } export namespace CardService { diff --git a/src/shared/suggestions.ts b/src/shared/suggestions.ts new file mode 100644 index 0000000..2a08a80 --- /dev/null +++ b/src/shared/suggestions.ts @@ -0,0 +1,3 @@ +export type Suggestions = { + items: string[]; +}; \ No newline at end of file diff --git a/src/shared/validation.ts b/src/shared/validation.ts new file mode 100644 index 0000000..22039e1 --- /dev/null +++ b/src/shared/validation.ts @@ -0,0 +1,4 @@ +export type Validation = { + characterLimit?: number; + inputType?: string; +}; \ No newline at end of file diff --git a/src/widgets/index.ts b/src/widgets/index.ts index cbdebb0..0021be7 100644 --- a/src/widgets/index.ts +++ b/src/widgets/index.ts @@ -8,10 +8,13 @@ import { SelectionInputBuilder } from './selection-input/selection-input.builder import { SelectionInputWidget } from './selection-input/selection-input.widget'; import { TextParagraphBuilder } from './text-paragraph/text-paragraph.builder'; import { TextParagraphWidget } from './text-paragraph/text-paragraph.widget'; +import { TextInputBuilder } from './text-input/text-input.builder'; +import { TextInputWidget } from './text-input/text-input.widget'; export type Widget = | SelectionInputWidget | TextParagraphWidget + | TextInputWidget | ButtonListWidget | DateTimePickerWidget | DividerWidget; @@ -19,6 +22,7 @@ export type Widget = export type WidgetBuilder = | SelectionInputBuilder | TextParagraphBuilder + | TextInputBuilder | ButtonListBuilder | DateTimePickerBuilder | DividerBuilder; diff --git a/src/widgets/text-input/text-input.builder.spec.ts b/src/widgets/text-input/text-input.builder.spec.ts new file mode 100644 index 0000000..e08040b --- /dev/null +++ b/src/widgets/text-input/text-input.builder.spec.ts @@ -0,0 +1,74 @@ +import { ActionBuilder } from '../../actions/action.builder'; +import { TextInputBuilder } from './text-input.builder'; + +describe('TextInputBuilder', () => { + let sut: TextInputBuilder; + + beforeEach(() => { + sut = new TextInputBuilder(); + }); + + test('throws error if name is not set', () => { + sut.setTitle('Test Label'); + expect(() => sut.build()).toThrow('TextInput name is required'); + }); + + test('throws error if label is not set', () => { + sut.setFieldName('testField'); + expect(() => sut.build()).toThrow('TextInput label is required'); + }); + + test('builds with required fields', () => { + sut.setFieldName('testField').setTitle('Test Label'); + const output = sut.build(); + expect(output.name).toBe('testField'); + expect(output.label).toBe('Test Label'); + }); + + test('sets hintText', () => { + sut.setFieldName('testField').setTitle('Test Label').setHint('Test hint'); + const output = sut.build(); + expect(output.hintText).toBe('Test hint'); + }); + + test('sets value', () => { + sut.setFieldName('testField').setTitle('Test Label').setValue('Test value'); + const output = sut.build(); + expect(output.value).toBe('Test value'); + }); + + test('sets type to MULTIPLE_LINE when multiline is true', () => { + sut.setFieldName('testField').setTitle('Test Label').setMultiline(true); + const output = sut.build(); + expect(output.type).toBe('MULTIPLE_LINE'); + }); + + test('sets type to SINGLE_LINE when multiline is false', () => { + sut.setFieldName('testField').setTitle('Test Label').setMultiline(false); + const output = sut.build(); + expect(output.type).toBe('SINGLE_LINE'); + }); + + test('sets onChange action', () => { + const action = new ActionBuilder().setFunctionName('testFunction'); + sut.setFieldName('testField').setTitle('Test Label').setOnChangeAction(action); + const output = sut.build(); + expect(output.onChangeAction).toBeDefined(); + expect(output.onChangeAction?.function).toBe('testFunction'); + }); + + test('sets initialSuggestions', () => { + const suggestions = { items: ['item1', 'item2'] }; + sut.setFieldName('testField').setTitle('Test Label').setSuggestions(suggestions); + const output = sut.build(); + expect(output.initialSuggestions).toBe(suggestions); + }); + + test('sets validation', () => { + const validation = { characterLimit: 100, inputType: 'email' }; + sut.setFieldName('testField').setTitle('Test Label').setValidation(validation); + const output = sut.build(); + expect(output.validation).toBe(validation); + }); + +}); \ No newline at end of file diff --git a/src/widgets/text-input/text-input.builder.ts b/src/widgets/text-input/text-input.builder.ts new file mode 100644 index 0000000..865c53a --- /dev/null +++ b/src/widgets/text-input/text-input.builder.ts @@ -0,0 +1,88 @@ +import { ActionBuilder } from '../../actions/action.builder'; +import { Suggestions } from '../../shared/suggestions'; +import { Validation } from '../../shared/validation'; +import { + TextInputWidget, + TextInputWidgetProps, +} from './text-input.widget'; + +type TextInputBuilderProps = { + name?: string; + label?: string; + hintText?: string; + value?: string; + multiline?: boolean; + onChangeActionBuilder?: ActionBuilder; + initialSuggestions?: Suggestions; + validation?: Validation; +}; + +export class TextInputBuilder { + private _props: TextInputBuilderProps = {}; + + public build(): TextInputWidget { + if (!this._props.name) { + throw new Error('TextInput name is required'); + } + if (!this._props.label) { + throw new Error('TextInput label is required'); + } + + const hasAction = !!this._props.onChangeActionBuilder; + const props: TextInputWidgetProps = { + name: this._props.name, + label: this._props.label, + hintText: this._props.hintText, + value: this._props.value, + type: this._props.multiline ? 'MULTIPLE_LINE' : 'SINGLE_LINE', + initialSuggestions: this._props.initialSuggestions, + validation: this._props.validation, + ...(hasAction && { + onChangeAction: this._props.onChangeActionBuilder!.build(), + }), + }; + + return new TextInputWidget(props); + } + + public setFieldName(fieldName: string): this { + this._props.name = fieldName; + return this; + } + + public setTitle(title: string): this { + this._props.label = title; + return this; + } + + public setHint(hint: string): this { + this._props.hintText = hint; + return this; + } + + public setValue(value: string): this { + this._props.value = value; + return this; + } + + public setMultiline(multiline: boolean): this { + this._props.multiline = multiline; + return this; + } + + public setOnChangeAction(action: ActionBuilder): this { + this._props.onChangeActionBuilder = action; + return this; + } + + public setSuggestions(suggestions: Suggestions): this { + this._props.initialSuggestions = suggestions; + return this; + } + + + public setValidation(validation: Validation): this { + this._props.validation = validation; + return this; + } +} \ No newline at end of file diff --git a/src/widgets/text-input/text-input.widget.ts b/src/widgets/text-input/text-input.widget.ts new file mode 100644 index 0000000..f446d3d --- /dev/null +++ b/src/widgets/text-input/text-input.widget.ts @@ -0,0 +1,39 @@ +import { Action } from '../../actions/action'; +import { Suggestions } from '../../shared/suggestions'; +import { Validation } from '../../shared/validation'; + +export type TextInputWidgetProps = { + name: string; + label: string; + hintText?: string; + value?: string; + type?: 'SINGLE_LINE' | 'MULTIPLE_LINE'; + onChangeAction?: Action; + initialSuggestions?: Suggestions; + validation?: Validation; + placeholderText?: string; +}; + +export class TextInputWidget { + public readonly name: string; + public readonly label: string; + public readonly hintText?: string; + public readonly value?: string; + public readonly type?: 'SINGLE_LINE' | 'MULTIPLE_LINE'; + public readonly onChangeAction?: Action; + public readonly initialSuggestions?: Suggestions; + public readonly validation?: Validation; + public readonly placeholderText?: string; + + public constructor(props: TextInputWidgetProps) { + this.name = props.name; + this.label = props.label; + this.hintText = props.hintText; + this.value = props.value; + this.type = props.type; + this.onChangeAction = props.onChangeAction; + this.initialSuggestions = props.initialSuggestions; + this.validation = props.validation; + this.placeholderText = props.placeholderText; + } +} \ No newline at end of file