From 771713764c14b6fc7eb45428735c02c80b8f92c9 Mon Sep 17 00:00:00 2001 From: Milena Serbinova <45200178+milesha@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:09:13 +0200 Subject: [PATCH 1/6] Add Input Field + Label Components (#11) Co-authored-by: Felix T.J. Dietrich Co-authored-by: Felix T.J. Dietrich --- webapp/src/app/ui/input/input.component.html | 13 +++ webapp/src/app/ui/input/input.component.ts | 51 ++++++++++++ webapp/src/app/ui/input/input.stories.ts | 86 ++++++++++++++++++++ webapp/src/app/ui/label/label.component.html | 6 ++ webapp/src/app/ui/label/label.component.ts | 25 ++++++ webapp/src/app/ui/label/label.stories.ts | 25 ++++++ 6 files changed, 206 insertions(+) create mode 100644 webapp/src/app/ui/input/input.component.html create mode 100644 webapp/src/app/ui/input/input.component.ts create mode 100644 webapp/src/app/ui/input/input.stories.ts create mode 100644 webapp/src/app/ui/label/label.component.html create mode 100644 webapp/src/app/ui/label/label.component.ts create mode 100644 webapp/src/app/ui/label/label.stories.ts diff --git a/webapp/src/app/ui/input/input.component.html b/webapp/src/app/ui/input/input.component.html new file mode 100644 index 00000000..8694176e --- /dev/null +++ b/webapp/src/app/ui/input/input.component.html @@ -0,0 +1,13 @@ + + + + + diff --git a/webapp/src/app/ui/input/input.component.ts b/webapp/src/app/ui/input/input.component.ts new file mode 100644 index 00000000..57683265 --- /dev/null +++ b/webapp/src/app/ui/input/input.component.ts @@ -0,0 +1,51 @@ +import { Component, computed, input, output } from '@angular/core'; +import type { ClassValue } from 'clsx'; +import { VariantProps } from 'class-variance-authority'; +import { cn } from 'app/utils'; +import { cva } from 'app/storybook.helper'; + +const [inputVariants, args, argTypes] = cva( + 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', + { + variants: { + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 px-2 py-1', + lg: 'h-11 px-4 py-3', + }, + }, + defaultVariants: { + size: 'default', + }, + }, +); + +export { args, argTypes }; + +interface InputVariants extends VariantProps {} + +@Component({ + selector: 'app-input', + standalone: true, + templateUrl: './input.component.html', +}) +export class AppInputComponent { + class = input(''); + type = input('text'); + placeholder = input(''); + size = input('default'); + disabled = input(false); + value = input(''); + id = input(''); + + valueChange = output(); + + onInput(event: Event) { + const inputValue = (event.target as HTMLInputElement).value; + this.valueChange.emit(inputValue); + } + + computedClass = computed(() => + cn(inputVariants({ size: this.size() }), this.class()), + ); +} diff --git a/webapp/src/app/ui/input/input.stories.ts b/webapp/src/app/ui/input/input.stories.ts new file mode 100644 index 00000000..8beb17ce --- /dev/null +++ b/webapp/src/app/ui/input/input.stories.ts @@ -0,0 +1,86 @@ +import { + argsToTemplate, + moduleMetadata, + type Meta, + type StoryObj, +} from '@storybook/angular'; +import { AppInputComponent, args, argTypes } from './input.component'; +import { action } from '@storybook/addon-actions'; +import { AppButtonComponent } from '@app/ui/button/button/button.component'; +import { AppLabelComponent } from '@app/ui/label/label.component'; + +const meta: Meta = { + title: 'UI/Input', + component: AppInputComponent, + tags: ['autodocs'], + args: { + ...args, + value: '', + disabled: false, + size: 'default', + }, + argTypes: { + ...argTypes, + disabled: { + control: 'boolean', + }, + onInput: { + action: 'onInput', + }, + }, + decorators: [ + moduleMetadata({ + imports: [AppButtonComponent, AppLabelComponent], + }), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: ``, + }), +}; + +export const Disabled: Story = { + args: { + disabled: true, + }, + render: (args) => ({ + props: args, + template: ``, + }), +}; + +export const WithLabel: Story = { + render: (args) => ({ + props: args, + template: ` +
+ Label + +
+ `, + }), +}; + +export const WithButton: Story = { + render: (args) => ({ + props: { + args, + userInput: '', + onButtonClick(value: string) { + action('Button Clicked')(`Input Value: ${value}`); + }, + }, + template: ` +
+ + Submit +
+ `, + }), +}; diff --git a/webapp/src/app/ui/label/label.component.html b/webapp/src/app/ui/label/label.component.html new file mode 100644 index 00000000..3eed1b2c --- /dev/null +++ b/webapp/src/app/ui/label/label.component.html @@ -0,0 +1,6 @@ + diff --git a/webapp/src/app/ui/label/label.component.ts b/webapp/src/app/ui/label/label.component.ts new file mode 100644 index 00000000..013d6cb8 --- /dev/null +++ b/webapp/src/app/ui/label/label.component.ts @@ -0,0 +1,25 @@ +import { Component, computed, input } from '@angular/core'; +import type { ClassValue } from 'clsx'; +import { VariantProps } from 'class-variance-authority'; +import { cn } from 'app/utils'; +import { cva } from 'app/storybook.helper'; + +const [labelVariants, args, argTypes] = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'); + +export { args, argTypes }; + +interface LabelVariants extends VariantProps {} + +@Component({ + selector: 'app-label', + standalone: true, + templateUrl: './label.component.html', +}) +export class AppLabelComponent { + class = input(''); + for = input(''); + + computedClass = computed(() => + cn(labelVariants({}), this.class()), + ); +} diff --git a/webapp/src/app/ui/label/label.stories.ts b/webapp/src/app/ui/label/label.stories.ts new file mode 100644 index 00000000..7d9dd734 --- /dev/null +++ b/webapp/src/app/ui/label/label.stories.ts @@ -0,0 +1,25 @@ +import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular'; +import { AppLabelComponent, args, argTypes } from './label.component'; + +const meta: Meta = { + title: 'UI/Label', + component: AppLabelComponent, + tags: ['autodocs'], + args: { + ...args, + for: 'example-input', + }, + argTypes: { + ...argTypes, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: `Label`, + }), +}; From b2e6178c8def3b61979854172b853dd78d8c947c Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Tue, 6 Aug 2024 19:10:15 +0200 Subject: [PATCH 2/6] Revert "Add Input Field + Label Components" (#37) --- webapp/src/app/ui/input/input.component.html | 13 --- webapp/src/app/ui/input/input.component.ts | 51 ------------ webapp/src/app/ui/input/input.stories.ts | 86 -------------------- webapp/src/app/ui/label/label.component.html | 6 -- webapp/src/app/ui/label/label.component.ts | 25 ------ webapp/src/app/ui/label/label.stories.ts | 25 ------ 6 files changed, 206 deletions(-) delete mode 100644 webapp/src/app/ui/input/input.component.html delete mode 100644 webapp/src/app/ui/input/input.component.ts delete mode 100644 webapp/src/app/ui/input/input.stories.ts delete mode 100644 webapp/src/app/ui/label/label.component.html delete mode 100644 webapp/src/app/ui/label/label.component.ts delete mode 100644 webapp/src/app/ui/label/label.stories.ts diff --git a/webapp/src/app/ui/input/input.component.html b/webapp/src/app/ui/input/input.component.html deleted file mode 100644 index 8694176e..00000000 --- a/webapp/src/app/ui/input/input.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/webapp/src/app/ui/input/input.component.ts b/webapp/src/app/ui/input/input.component.ts deleted file mode 100644 index 57683265..00000000 --- a/webapp/src/app/ui/input/input.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, computed, input, output } from '@angular/core'; -import type { ClassValue } from 'clsx'; -import { VariantProps } from 'class-variance-authority'; -import { cn } from 'app/utils'; -import { cva } from 'app/storybook.helper'; - -const [inputVariants, args, argTypes] = cva( - 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', - { - variants: { - size: { - default: 'h-10 px-4 py-2', - sm: 'h-9 px-2 py-1', - lg: 'h-11 px-4 py-3', - }, - }, - defaultVariants: { - size: 'default', - }, - }, -); - -export { args, argTypes }; - -interface InputVariants extends VariantProps {} - -@Component({ - selector: 'app-input', - standalone: true, - templateUrl: './input.component.html', -}) -export class AppInputComponent { - class = input(''); - type = input('text'); - placeholder = input(''); - size = input('default'); - disabled = input(false); - value = input(''); - id = input(''); - - valueChange = output(); - - onInput(event: Event) { - const inputValue = (event.target as HTMLInputElement).value; - this.valueChange.emit(inputValue); - } - - computedClass = computed(() => - cn(inputVariants({ size: this.size() }), this.class()), - ); -} diff --git a/webapp/src/app/ui/input/input.stories.ts b/webapp/src/app/ui/input/input.stories.ts deleted file mode 100644 index 8beb17ce..00000000 --- a/webapp/src/app/ui/input/input.stories.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - argsToTemplate, - moduleMetadata, - type Meta, - type StoryObj, -} from '@storybook/angular'; -import { AppInputComponent, args, argTypes } from './input.component'; -import { action } from '@storybook/addon-actions'; -import { AppButtonComponent } from '@app/ui/button/button/button.component'; -import { AppLabelComponent } from '@app/ui/label/label.component'; - -const meta: Meta = { - title: 'UI/Input', - component: AppInputComponent, - tags: ['autodocs'], - args: { - ...args, - value: '', - disabled: false, - size: 'default', - }, - argTypes: { - ...argTypes, - disabled: { - control: 'boolean', - }, - onInput: { - action: 'onInput', - }, - }, - decorators: [ - moduleMetadata({ - imports: [AppButtonComponent, AppLabelComponent], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: (args) => ({ - props: args, - template: ``, - }), -}; - -export const Disabled: Story = { - args: { - disabled: true, - }, - render: (args) => ({ - props: args, - template: ``, - }), -}; - -export const WithLabel: Story = { - render: (args) => ({ - props: args, - template: ` -
- Label - -
- `, - }), -}; - -export const WithButton: Story = { - render: (args) => ({ - props: { - args, - userInput: '', - onButtonClick(value: string) { - action('Button Clicked')(`Input Value: ${value}`); - }, - }, - template: ` -
- - Submit -
- `, - }), -}; diff --git a/webapp/src/app/ui/label/label.component.html b/webapp/src/app/ui/label/label.component.html deleted file mode 100644 index 3eed1b2c..00000000 --- a/webapp/src/app/ui/label/label.component.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/webapp/src/app/ui/label/label.component.ts b/webapp/src/app/ui/label/label.component.ts deleted file mode 100644 index 013d6cb8..00000000 --- a/webapp/src/app/ui/label/label.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, computed, input } from '@angular/core'; -import type { ClassValue } from 'clsx'; -import { VariantProps } from 'class-variance-authority'; -import { cn } from 'app/utils'; -import { cva } from 'app/storybook.helper'; - -const [labelVariants, args, argTypes] = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'); - -export { args, argTypes }; - -interface LabelVariants extends VariantProps {} - -@Component({ - selector: 'app-label', - standalone: true, - templateUrl: './label.component.html', -}) -export class AppLabelComponent { - class = input(''); - for = input(''); - - computedClass = computed(() => - cn(labelVariants({}), this.class()), - ); -} diff --git a/webapp/src/app/ui/label/label.stories.ts b/webapp/src/app/ui/label/label.stories.ts deleted file mode 100644 index 7d9dd734..00000000 --- a/webapp/src/app/ui/label/label.stories.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular'; -import { AppLabelComponent, args, argTypes } from './label.component'; - -const meta: Meta = { - title: 'UI/Label', - component: AppLabelComponent, - tags: ['autodocs'], - args: { - ...args, - for: 'example-input', - }, - argTypes: { - ...argTypes, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: (args) => ({ - props: args, - template: `Label`, - }), -}; From 9e90f7f79013bb4837d5900f21f7333399e85ce1 Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Tue, 6 Aug 2024 19:12:32 +0200 Subject: [PATCH 3/6] Add Input Field + Label Components (#38) Co-authored-by: milenasrb --- webapp/src/app/ui/input/input.component.html | 13 +++ webapp/src/app/ui/input/input.component.ts | 51 ++++++++++++ webapp/src/app/ui/input/input.stories.ts | 86 ++++++++++++++++++++ webapp/src/app/ui/label/label.component.html | 6 ++ webapp/src/app/ui/label/label.component.ts | 25 ++++++ webapp/src/app/ui/label/label.stories.ts | 25 ++++++ 6 files changed, 206 insertions(+) create mode 100644 webapp/src/app/ui/input/input.component.html create mode 100644 webapp/src/app/ui/input/input.component.ts create mode 100644 webapp/src/app/ui/input/input.stories.ts create mode 100644 webapp/src/app/ui/label/label.component.html create mode 100644 webapp/src/app/ui/label/label.component.ts create mode 100644 webapp/src/app/ui/label/label.stories.ts diff --git a/webapp/src/app/ui/input/input.component.html b/webapp/src/app/ui/input/input.component.html new file mode 100644 index 00000000..8694176e --- /dev/null +++ b/webapp/src/app/ui/input/input.component.html @@ -0,0 +1,13 @@ + + + + + diff --git a/webapp/src/app/ui/input/input.component.ts b/webapp/src/app/ui/input/input.component.ts new file mode 100644 index 00000000..57683265 --- /dev/null +++ b/webapp/src/app/ui/input/input.component.ts @@ -0,0 +1,51 @@ +import { Component, computed, input, output } from '@angular/core'; +import type { ClassValue } from 'clsx'; +import { VariantProps } from 'class-variance-authority'; +import { cn } from 'app/utils'; +import { cva } from 'app/storybook.helper'; + +const [inputVariants, args, argTypes] = cva( + 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', + { + variants: { + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 px-2 py-1', + lg: 'h-11 px-4 py-3', + }, + }, + defaultVariants: { + size: 'default', + }, + }, +); + +export { args, argTypes }; + +interface InputVariants extends VariantProps {} + +@Component({ + selector: 'app-input', + standalone: true, + templateUrl: './input.component.html', +}) +export class AppInputComponent { + class = input(''); + type = input('text'); + placeholder = input(''); + size = input('default'); + disabled = input(false); + value = input(''); + id = input(''); + + valueChange = output(); + + onInput(event: Event) { + const inputValue = (event.target as HTMLInputElement).value; + this.valueChange.emit(inputValue); + } + + computedClass = computed(() => + cn(inputVariants({ size: this.size() }), this.class()), + ); +} diff --git a/webapp/src/app/ui/input/input.stories.ts b/webapp/src/app/ui/input/input.stories.ts new file mode 100644 index 00000000..8beb17ce --- /dev/null +++ b/webapp/src/app/ui/input/input.stories.ts @@ -0,0 +1,86 @@ +import { + argsToTemplate, + moduleMetadata, + type Meta, + type StoryObj, +} from '@storybook/angular'; +import { AppInputComponent, args, argTypes } from './input.component'; +import { action } from '@storybook/addon-actions'; +import { AppButtonComponent } from '@app/ui/button/button/button.component'; +import { AppLabelComponent } from '@app/ui/label/label.component'; + +const meta: Meta = { + title: 'UI/Input', + component: AppInputComponent, + tags: ['autodocs'], + args: { + ...args, + value: '', + disabled: false, + size: 'default', + }, + argTypes: { + ...argTypes, + disabled: { + control: 'boolean', + }, + onInput: { + action: 'onInput', + }, + }, + decorators: [ + moduleMetadata({ + imports: [AppButtonComponent, AppLabelComponent], + }), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: ``, + }), +}; + +export const Disabled: Story = { + args: { + disabled: true, + }, + render: (args) => ({ + props: args, + template: ``, + }), +}; + +export const WithLabel: Story = { + render: (args) => ({ + props: args, + template: ` +
+ Label + +
+ `, + }), +}; + +export const WithButton: Story = { + render: (args) => ({ + props: { + args, + userInput: '', + onButtonClick(value: string) { + action('Button Clicked')(`Input Value: ${value}`); + }, + }, + template: ` +
+ + Submit +
+ `, + }), +}; diff --git a/webapp/src/app/ui/label/label.component.html b/webapp/src/app/ui/label/label.component.html new file mode 100644 index 00000000..3eed1b2c --- /dev/null +++ b/webapp/src/app/ui/label/label.component.html @@ -0,0 +1,6 @@ + diff --git a/webapp/src/app/ui/label/label.component.ts b/webapp/src/app/ui/label/label.component.ts new file mode 100644 index 00000000..013d6cb8 --- /dev/null +++ b/webapp/src/app/ui/label/label.component.ts @@ -0,0 +1,25 @@ +import { Component, computed, input } from '@angular/core'; +import type { ClassValue } from 'clsx'; +import { VariantProps } from 'class-variance-authority'; +import { cn } from 'app/utils'; +import { cva } from 'app/storybook.helper'; + +const [labelVariants, args, argTypes] = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'); + +export { args, argTypes }; + +interface LabelVariants extends VariantProps {} + +@Component({ + selector: 'app-label', + standalone: true, + templateUrl: './label.component.html', +}) +export class AppLabelComponent { + class = input(''); + for = input(''); + + computedClass = computed(() => + cn(labelVariants({}), this.class()), + ); +} diff --git a/webapp/src/app/ui/label/label.stories.ts b/webapp/src/app/ui/label/label.stories.ts new file mode 100644 index 00000000..7d9dd734 --- /dev/null +++ b/webapp/src/app/ui/label/label.stories.ts @@ -0,0 +1,25 @@ +import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular'; +import { AppLabelComponent, args, argTypes } from './label.component'; + +const meta: Meta = { + title: 'UI/Label', + component: AppLabelComponent, + tags: ['autodocs'], + args: { + ...args, + for: 'example-input', + }, + argTypes: { + ...argTypes, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: `Label`, + }), +}; From 29a1350fae9c4dee6ce3cd5fc7f7abae57c3124f Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Tue, 6 Aug 2024 19:17:39 +0200 Subject: [PATCH 4/6] Fix linting issues and run prettier (#36) --- webapp/eslint.config.js | 6 +- webapp/src/app/app.component.html | 8 +-- webapp/src/app/app.component.ts | 2 +- webapp/src/app/app.config.ts | 12 ++-- .../example/counter/counter.component.html | 24 ++++--- .../app/example/counter/counter.component.ts | 19 +++--- .../app/example/counter/counter.stories.ts | 10 +-- webapp/src/app/example/counter/counter.ts | 6 +- .../src/app/example/hello/hello.component.ts | 24 ++++--- webapp/src/app/storybook.helper.ts | 46 ++++++------- .../ui/button/button/button.component.html | 2 +- .../app/ui/button/button/button.component.ts | 16 ++--- .../app/ui/button/button/button.stories.ts | 66 +++++++++---------- webapp/src/app/ui/input/input.component.html | 14 +--- webapp/src/app/ui/input/input.component.ts | 16 ++--- webapp/src/app/ui/input/input.stories.ts | 41 +++++------- webapp/src/app/ui/label/label.component.html | 7 +- webapp/src/app/ui/label/label.component.ts | 9 +-- webapp/src/app/ui/label/label.stories.ts | 10 +-- webapp/src/app/utils.ts | 6 +- webapp/src/index.html | 20 +++--- webapp/src/main.ts | 3 +- webapp/src/styles.css | 2 +- 23 files changed, 166 insertions(+), 203 deletions(-) diff --git a/webapp/eslint.config.js b/webapp/eslint.config.js index 5329136c..3ecbff00 100644 --- a/webapp/eslint.config.js +++ b/webapp/eslint.config.js @@ -8,6 +8,7 @@ module.exports = [ { ignores: [ '.cache/', + '.angular/', '.git/', '.github/', 'build/', @@ -54,10 +55,7 @@ module.exports = [ style: 'kebab-case', }, ], - '@angular-eslint/prefer-standalone': 'error', - '@angular-eslint/template/prefer-ngsrc': 'error', - '@angular-eslint/template/prefer-self-closing-tags': 'error', - '@angular-eslint/template/prefer-control-flow': 'error', + '@typescript-eslint/no-empty-object-type': 'off', }, }, { diff --git a/webapp/src/app/app.component.html b/webapp/src/app/app.component.html index 55ae350d..460bba46 100644 --- a/webapp/src/app/app.component.html +++ b/webapp/src/app/app.component.html @@ -3,9 +3,9 @@

Header

- - + +
- + - \ No newline at end of file + diff --git a/webapp/src/app/app.component.ts b/webapp/src/app/app.component.ts index 3bf1d72a..513811ab 100644 --- a/webapp/src/app/app.component.ts +++ b/webapp/src/app/app.component.ts @@ -8,7 +8,7 @@ import { HelloComponent } from './example/hello/hello.component'; standalone: true, imports: [RouterOutlet, CounterComponent, HelloComponent], templateUrl: './app.component.html', - styles: [], + styles: [] }) export class AppComponent { title = 'Hephaestus'; diff --git a/webapp/src/app/app.config.ts b/webapp/src/app/app.config.ts index fc69a29e..c3b646d2 100644 --- a/webapp/src/app/app.config.ts +++ b/webapp/src/app/app.config.ts @@ -1,22 +1,18 @@ import { ApplicationConfig, importProvidersFrom, provideExperimentalZonelessChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { - provideAngularQuery, - QueryClient, -} from '@tanstack/angular-query-experimental' -import {LucideAngularModule, Home } from "lucide-angular"; +import { provideAngularQuery, QueryClient } from '@tanstack/angular-query-experimental'; +import { LucideAngularModule, Home } from 'lucide-angular'; import { routes } from './app.routes'; import { BASE_PATH } from './core/modules/openapi'; - export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), provideRouter(routes), provideAngularQuery(new QueryClient()), - { provide: BASE_PATH, useValue: "http://localhost:8080" }, + { provide: BASE_PATH, useValue: 'http://localhost:8080' }, provideHttpClient(withInterceptorsFromDi()), importProvidersFrom(LucideAngularModule.pick({ Home })) ] -}; \ No newline at end of file +}; diff --git a/webapp/src/app/example/counter/counter.component.html b/webapp/src/app/example/counter/counter.component.html index 87e73a15..9c3d46a6 100644 --- a/webapp/src/app/example/counter/counter.component.html +++ b/webapp/src/app/example/counter/counter.component.html @@ -1,17 +1,21 @@
-

Counter Title: {{title()}}

-

Value: {{counter()}}

-

HexValue: {{hexCounter()}}

- - Increment - @if (byCount() > 1) { - by {{byCount()}} - } - +

Counter Title: {{ title() }}

+

Value: {{ counter() }}

+

+

HexValue: {{ hexCounter() }}

+

+ + Increment + @if (byCount() > 1) { + by {{ byCount() }} + } + +

+

History

@for (item of counterHistory(); track item.dec) { {{ item.dec }} }
-
\ No newline at end of file + diff --git a/webapp/src/app/example/counter/counter.component.ts b/webapp/src/app/example/counter/counter.component.ts index fb2eb490..0b3fb872 100644 --- a/webapp/src/app/example/counter/counter.component.ts +++ b/webapp/src/app/example/counter/counter.component.ts @@ -8,10 +8,10 @@ interface CounterHistoryEntry { } @Component({ - selector: 'counter', + selector: 'app-counter', standalone: true, imports: [AppButtonComponent], - templateUrl: './counter.component.html', + templateUrl: './counter.component.html' }) export class CounterComponent { // we put all our application data inside signals! -> most optimal change detection and re-rendering possible @@ -26,9 +26,9 @@ export class CounterComponent { // Pitfall: conditional logic inside computed // When calling computed initially the dependency signal has to be called, otherwise it will not work return this.counter().toString(16); - }) + }); - counterHistory = signal([{ dec: 0, hex: "0" }]) + counterHistory = signal([{ dec: 0, hex: '0' }]); constructor() { console.log(`counter value: ${this.counter()}`); @@ -36,7 +36,8 @@ export class CounterComponent { // `effect`s goes in constructor (?) // `effectRef` is not necessary, only if needed // Runs once when the effect is declared to collect dependencies, and again when they change - const effectRef = effect((onCleanup) => { + // const effectRef = + effect((onCleanup) => { const currentCount = this.counter(); const currentHexCount = this.hexCounter(); @@ -46,8 +47,8 @@ export class CounterComponent { onCleanup(() => { console.log('Perform cleanup action here'); - }) - }) + }); + }); // effectRef.destroy() at any time! Usually not necessary @@ -57,9 +58,9 @@ export class CounterComponent { increment() { console.log('Updating counter...'); - this.counter.update(counter => counter + this.byCount()); + this.counter.update((counter) => counter + this.byCount()); // Update values of a signal only through Signals API, e.g.`set()` and `update()`, not directly (i.e. `push()`) - this.counterHistory.update(history => [...history, { dec: this.counter(), hex: this.hexCounter() }]) + this.counterHistory.update((history) => [...history, { dec: this.counter(), hex: this.hexCounter() }]); } } diff --git a/webapp/src/app/example/counter/counter.stories.ts b/webapp/src/app/example/counter/counter.stories.ts index 8f8504b5..a751c49e 100644 --- a/webapp/src/app/example/counter/counter.stories.ts +++ b/webapp/src/app/example/counter/counter.stories.ts @@ -8,9 +8,9 @@ const meta: Meta = { component: CounterComponent, tags: ['autodocs'], argTypes: toArgs({ - title: "test", - byCount: 2, - }), + title: 'test', + byCount: 2 + }) }; export default meta; @@ -21,5 +21,5 @@ export const Primary: Story = { args: { title: 'Counter', byCount: 2 - }, -}; \ No newline at end of file + } +}; diff --git a/webapp/src/app/example/counter/counter.ts b/webapp/src/app/example/counter/counter.ts index a16ed0a6..93cf9c2b 100644 --- a/webapp/src/app/example/counter/counter.ts +++ b/webapp/src/app/example/counter/counter.ts @@ -1,4 +1,4 @@ -import { signal } from "@angular/core"; +import { signal } from '@angular/core'; export const counter = signal(0); @@ -18,9 +18,9 @@ export const counter = signal(0); // // inject any dependencies you need here // } // -// // anyone needing to modify the signal +// // anyone needing to modify the signal // // needs to do so in a controlled way // incrementCounter() { // this.counterSignal.update((val) => val + 1); // } -// } \ No newline at end of file +// } diff --git a/webapp/src/app/example/hello/hello.component.ts b/webapp/src/app/example/hello/hello.component.ts index 30c42c61..12a35298 100644 --- a/webapp/src/app/example/hello/hello.component.ts +++ b/webapp/src/app/example/hello/hello.component.ts @@ -1,36 +1,34 @@ import { Component, inject } from '@angular/core'; import { injectMutation, injectQuery, injectQueryClient } from '@tanstack/angular-query-experimental'; -import { AngularQueryDevtools } from '@tanstack/angular-query-devtools-experimental' +import { AngularQueryDevtools } from '@tanstack/angular-query-devtools-experimental'; import { lastValueFrom } from 'rxjs'; import { HelloService } from 'app/core/modules/openapi'; import { AppButtonComponent } from 'app/ui/button/button/button.component'; @Component({ - selector: 'hello', + selector: 'app-hello', standalone: true, imports: [AppButtonComponent, AngularQueryDevtools], templateUrl: './hello.component.html' }) export class HelloComponent { - helloService = inject(HelloService) - queryClient = injectQueryClient() + helloService = inject(HelloService); + queryClient = injectQueryClient(); query = injectQuery(() => ({ queryKey: ['hellos'], - queryFn: async () => lastValueFrom(this.helloService.getAllHellos()), - }) - ); + queryFn: async () => lastValueFrom(this.helloService.getAllHellos()) + })); mutation = injectMutation(() => ({ mutationFn: () => lastValueFrom(this.helloService.addHello()), - onSuccess: () => + onSuccess: () => this.queryClient.invalidateQueries({ - queryKey: ['hellos'], - }), - }) - ); + queryKey: ['hellos'] + }) + })); addHello() { this.mutation.mutate(); } -} \ No newline at end of file +} diff --git a/webapp/src/app/storybook.helper.ts b/webapp/src/app/storybook.helper.ts index a269d051..49322ae7 100644 --- a/webapp/src/app/storybook.helper.ts +++ b/webapp/src/app/storybook.helper.ts @@ -1,13 +1,10 @@ import { InputSignalWithTransform, InputSignal, EventEmitter } from '@angular/core'; -import { Args, ArgTypes } from '@storybook/angular'; import { cva as CVA } from 'class-variance-authority'; import { ClassProp, ClassValue, StringToBoolean } from 'class-variance-authority/types'; // Source: // https://stackoverflow.com/questions/78379300/how-do-i-use-angular-input-signals-with-storybook -export function toArgs( - args: Partial>> -): TransformEventType { +export function toArgs(args: Partial>>): TransformEventType { return args as unknown as TransformEventType; } @@ -22,28 +19,23 @@ type TransformSignalInputType = { }; // Type to extract the type from InputSignal or InputSignalWithTransform -type TransformInputType = - T extends InputSignalWithTransform - ? U - : T extends InputSignal - ? U - : T; - - +type TransformInputType = T extends InputSignalWithTransform ? U : T extends InputSignal ? U : T; // CVA Storybook Helper type ConfigSchema = Record>; type ConfigVariants = { - [Variant in keyof T]?: StringToBoolean | null | undefined; + [Variant in keyof T]?: StringToBoolean | null | undefined; }; type ConfigVariantsMulti = { - [Variant in keyof T]?: StringToBoolean | StringToBoolean[] | undefined; + [Variant in keyof T]?: StringToBoolean | StringToBoolean[] | undefined; }; -type Config = T extends ConfigSchema ? { - variants?: T; - defaultVariants?: ConfigVariants; - compoundVariants?: (T extends ConfigSchema ? (ConfigVariants | ConfigVariantsMulti) & ClassProp : ClassProp)[]; -} : never; +type Config = T extends ConfigSchema + ? { + variants?: T; + defaultVariants?: ConfigVariants; + compoundVariants?: (T extends ConfigSchema ? (ConfigVariants | ConfigVariantsMulti) & ClassProp : ClassProp)[]; + } + : never; function createCVAArgTypes(config?: Config) { if (!config?.variants) { @@ -53,12 +45,14 @@ function createCVAArgTypes(config?: Config) { const variants = config?.variants; return Object.fromEntries( Object.entries(variants).map(([variant, options]) => { - const variantKey = variant as keyof T; - const optionsArray = Object.keys(options) as (keyof T[typeof variantKey])[]; - return [variant, { - control: { type: 'select' }, - options: optionsArray - }]; + const optionsArray = Object.keys(options) as (keyof T[typeof variant])[]; + return [ + variant, + { + control: { type: 'select' }, + options: optionsArray + } + ]; }) ); } @@ -66,7 +60,7 @@ function createCVAArgTypes(config?: Config) { function createCVADefaultArgs(config?: Config) { return (config?.defaultVariants || {}) as { [Variant in keyof T]: keyof T[Variant]; - } + }; } export function cva(base?: ClassValue, config?: Config) { diff --git a/webapp/src/app/ui/button/button/button.component.html b/webapp/src/app/ui/button/button/button.component.html index 8677d1db..57c21bac 100644 --- a/webapp/src/app/ui/button/button/button.component.html +++ b/webapp/src/app/ui/button/button/button.component.html @@ -1,3 +1,3 @@ \ No newline at end of file + diff --git a/webapp/src/app/ui/button/button/button.component.ts b/webapp/src/app/ui/button/button/button.component.ts index 8d3a7235..fa921b14 100644 --- a/webapp/src/app/ui/button/button/button.component.ts +++ b/webapp/src/app/ui/button/button/button.component.ts @@ -14,19 +14,19 @@ const [buttonVariants, args, argTypes] = cva( outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'text-primary underline-offset-4 hover:underline', + link: 'text-primary underline-offset-4 hover:underline' }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', - icon: 'h-10 w-10', - }, + icon: 'h-10 w-10' + } }, defaultVariants: { variant: 'default', - size: 'default', - }, + size: 'default' + } } ); @@ -37,10 +37,10 @@ interface ButtonVariants extends VariantProps {} @Component({ selector: 'app-button', standalone: true, - templateUrl: './button.component.html', + templateUrl: './button.component.html' }) export class AppButtonComponent { - class = input(''); + class = input(''); variant = input('default'); size = input('default'); disabled = input(false); @@ -48,4 +48,4 @@ export class AppButtonComponent { onClick = output(); computedClass = computed(() => cn(buttonVariants({ variant: this.variant(), size: this.size() }), this.class())); -} \ No newline at end of file +} diff --git a/webapp/src/app/ui/button/button/button.stories.ts b/webapp/src/app/ui/button/button/button.stories.ts index 2c7c7158..a3b254cb 100644 --- a/webapp/src/app/ui/button/button/button.stories.ts +++ b/webapp/src/app/ui/button/button/button.stories.ts @@ -11,17 +11,17 @@ const meta: Meta = { args: { ...args, disabled: false, - onClick: fn(), + onClick: fn() }, argTypes: { ...argTypes, disabled: { - control: 'boolean', + control: 'boolean' }, onClick: { - action: 'onClick', - }, - }, + action: 'onClick' + } + } }; export default meta; @@ -36,17 +36,17 @@ export const Primary: Story = { }, render: (args) => ({ props: args, - template: `Primary`, + template: `Primary` }) }; export const Secondary: Story = { args: { - variant: "secondary", - size: "default" + variant: 'secondary', + size: 'default' }, - render: args => ({ + render: (args) => ({ props: args, template: `Secondary` }) @@ -54,11 +54,11 @@ export const Secondary: Story = { export const Destructive: Story = { args: { - variant: "destructive", - size: "default" + variant: 'destructive', + size: 'default' }, - render: args => ({ + render: (args) => ({ props: args, template: `Destructive` }) @@ -66,11 +66,11 @@ export const Destructive: Story = { export const Outline: Story = { args: { - variant: "outline", - size: "default" + variant: 'outline', + size: 'default' }, - render: args => ({ + render: (args) => ({ props: args, template: `Outline` }) @@ -78,11 +78,11 @@ export const Outline: Story = { export const Ghost: Story = { args: { - variant: "ghost", - size: "default" + variant: 'ghost', + size: 'default' }, - render: args => ({ + render: (args) => ({ props: args, template: `Ghost` }) @@ -90,11 +90,11 @@ export const Ghost: Story = { export const Link: Story = { args: { - variant: "link", - size: "default" + variant: 'link', + size: 'default' }, - render: args => ({ + render: (args) => ({ props: args, template: `Link` }) @@ -103,45 +103,45 @@ export const Link: Story = { export const Icon: Story = { decorators: [ moduleMetadata({ - imports: [LucideAngularModule.pick({ ChevronRight })], - }), + imports: [LucideAngularModule.pick({ ChevronRight })] + }) ], render: (args) => ({ props: { variant: 'outline', - size: 'icon', + size: 'icon' }, - template: ``, + template: `` }) }; export const WithIcon: Story = { decorators: [ moduleMetadata({ - imports: [LucideAngularModule.pick({ Mail })], - }), + imports: [LucideAngularModule.pick({ Mail })] + }) ], render: (args) => ({ props: { variant: 'default', - size: 'default', + size: 'default' }, - template: `Login with Email`, + template: `Login with Email` }) }; export const Loading: Story = { decorators: [ moduleMetadata({ - imports: [LucideAngularModule.pick({ Loader2 })], - }), + imports: [LucideAngularModule.pick({ Loader2 })] + }) ], render: (args) => ({ props: { variant: 'default', size: 'default', - disabled: true, + disabled: true }, - template: `Please wait`, + template: `Please wait` }) }; diff --git a/webapp/src/app/ui/input/input.component.html b/webapp/src/app/ui/input/input.component.html index 8694176e..6b64b478 100644 --- a/webapp/src/app/ui/input/input.component.html +++ b/webapp/src/app/ui/input/input.component.html @@ -1,13 +1 @@ - - - - - + diff --git a/webapp/src/app/ui/input/input.component.ts b/webapp/src/app/ui/input/input.component.ts index 57683265..bd46e268 100644 --- a/webapp/src/app/ui/input/input.component.ts +++ b/webapp/src/app/ui/input/input.component.ts @@ -11,13 +11,13 @@ const [inputVariants, args, argTypes] = cva( size: { default: 'h-10 px-4 py-2', sm: 'h-9 px-2 py-1', - lg: 'h-11 px-4 py-3', - }, + lg: 'h-11 px-4 py-3' + } }, defaultVariants: { - size: 'default', - }, - }, + size: 'default' + } + } ); export { args, argTypes }; @@ -27,7 +27,7 @@ interface InputVariants extends VariantProps {} @Component({ selector: 'app-input', standalone: true, - templateUrl: './input.component.html', + templateUrl: './input.component.html' }) export class AppInputComponent { class = input(''); @@ -45,7 +45,5 @@ export class AppInputComponent { this.valueChange.emit(inputValue); } - computedClass = computed(() => - cn(inputVariants({ size: this.size() }), this.class()), - ); + computedClass = computed(() => cn(inputVariants({ size: this.size() }), this.class())); } diff --git a/webapp/src/app/ui/input/input.stories.ts b/webapp/src/app/ui/input/input.stories.ts index 8beb17ce..3ce17104 100644 --- a/webapp/src/app/ui/input/input.stories.ts +++ b/webapp/src/app/ui/input/input.stories.ts @@ -1,9 +1,4 @@ -import { - argsToTemplate, - moduleMetadata, - type Meta, - type StoryObj, -} from '@storybook/angular'; +import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; import { AppInputComponent, args, argTypes } from './input.component'; import { action } from '@storybook/addon-actions'; import { AppButtonComponent } from '@app/ui/button/button/button.component'; @@ -17,22 +12,22 @@ const meta: Meta = { ...args, value: '', disabled: false, - size: 'default', + size: 'default' }, argTypes: { ...argTypes, disabled: { - control: 'boolean', + control: 'boolean' }, onInput: { - action: 'onInput', - }, + action: 'onInput' + } }, decorators: [ moduleMetadata({ - imports: [AppButtonComponent, AppLabelComponent], - }), - ], + imports: [AppButtonComponent, AppLabelComponent] + }) + ] }; export default meta; @@ -41,18 +36,18 @@ type Story = StoryObj; export const Default: Story = { render: (args) => ({ props: args, - template: ``, - }), + template: `` + }) }; export const Disabled: Story = { args: { - disabled: true, + disabled: true }, render: (args) => ({ props: args, - template: ``, - }), + template: `` + }) }; export const WithLabel: Story = { @@ -63,8 +58,8 @@ export const WithLabel: Story = { Label - `, - }), + ` + }) }; export const WithButton: Story = { @@ -74,13 +69,13 @@ export const WithButton: Story = { userInput: '', onButtonClick(value: string) { action('Button Clicked')(`Input Value: ${value}`); - }, + } }, template: `
Submit
- `, - }), + ` + }) }; diff --git a/webapp/src/app/ui/label/label.component.html b/webapp/src/app/ui/label/label.component.html index 3eed1b2c..72d84052 100644 --- a/webapp/src/app/ui/label/label.component.html +++ b/webapp/src/app/ui/label/label.component.html @@ -1,6 +1,3 @@ -