feat: implement new text field and button components; remove deprecated search bar and input controls

This commit is contained in:
Lorenz Hilpert
2025-03-03 21:02:21 +01:00
parent da5151df78
commit 73be50e7d2
47 changed files with 4640 additions and 150 deletions

View File

@@ -0,0 +1,21 @@
import type { StorybookConfig } from '@storybook/angular';
const config: StorybookConfig = {
stories: ['../stories/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
staticDirs: ['../src/assets'],
previewHead: (head) => `
${head}
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
`,
framework: {
name: '@storybook/angular',
options: {},
},
};
export default config;
// To customize your webpack configuration you can use the webpackFinal field.
// Check https://storybook.js.org/docs/react/builders/webpack#extending-storybooks-webpack-config
// and https://nx.dev/recipes/storybook/custom-builder-configs

View File

@@ -0,0 +1,21 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true
},
"exclude": ["../**/*.spec.ts"],
"include": [
// "../src/**/*.stories.ts",
// "../src/**/*.stories.js",
// "../src/**/*.stories.jsx",
// "../src/**/*.stories.tsx",
// "../src/**/*.stories.mdx",
"../stories/**/*.stories.ts",
"../stories/**/*.stories.js",
"../stories/**/*.stories.jsx",
"../stories/**/*.stories.tsx",
"../stories/**/*.stories.mdx",
"*.js",
"*.ts"
]
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"name": "isa-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/isa-app/src",
@@ -105,6 +105,56 @@
"staticFilePath": "dist/apps/isa-app/browser",
"spa": true
}
},
"storybook": {
"executor": "@storybook/angular:start-storybook",
"options": {
"port": 4400,
"configDir": "apps/isa-app/.storybook",
"browserTarget": "isa-app:build",
"compodoc": false,
"styles": ["apps/isa-app/src/styles.scss"]
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@storybook/angular:build-storybook",
"outputs": ["{options.outputDir}"],
"options": {
"outputDir": "dist/storybook/isa-app",
"configDir": "apps/isa-app/.storybook",
"browserTarget": "isa-app:build",
"compodoc": false
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"test-storybook": {
"executor": "nx:run-commands",
"options": {
"command": "test-storybook -c apps/isa-app/.storybook --url=http://localhost:4400"
}
},
"static-storybook": {
"executor": "@nx/web:file-server",
"dependsOn": ["build-storybook"],
"options": {
"buildTarget": "isa-app:build-storybook",
"staticFilePath": "dist/storybook/isa-app",
"spa": true
},
"configurations": {
"ci": {
"buildTarget": "isa-app:build-storybook:ci"
}
}
}
}
}

View File

@@ -0,0 +1,65 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { ButtonColor, ButtonSize, UiButtonComponent } from '@isa/ui/buttons';
// import { within } from '@storybook/testing-library';
// import { expect } from '@storybook/jest';
type UiButtonComponentInputs = {
color: ButtonColor;
size: ButtonSize;
pending: boolean;
disabled: boolean;
};
const meta: Meta<UiButtonComponentInputs> = {
component: UiButtonComponent,
title: 'ui/buttons/Button',
argTypes: {
color: {
control: 'select',
options: ['primary', 'secondary', 'brand', 'tertiary'] as ButtonColor[],
},
size: {
control: 'select',
options: ['small', 'medium', 'large'] as ButtonSize[],
},
pending: {
control: 'boolean',
},
disabled: {
control: 'boolean',
},
},
render: (args) => ({
props: args,
template: `<ui-button ${argsToTemplate(args, { exclude: ['disabled'] })} ${args.disabled ? 'disabled' : ''}>Button</ui-button>`,
}),
};
export default meta;
type Story = StoryObj<UiButtonComponent>;
export const Default: Story = {
args: {},
};
// export const Heading: Story = {
// args: {},
// play: async ({ canvasElement }) => {
// const canvas = within(canvasElement);
// expect(canvas.getByText(/ui-button works!/gi)).toBeTruthy();
// },
// };
export const PrimarySmall: Story = {
args: {
color: 'primary',
size: 'small',
},
};
export const PrimaryLarge: Story = {
args: {
color: 'primary',
size: 'large',
},
};

View File

@@ -0,0 +1,58 @@
import { argsToTemplate, type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';
import { IconButtonColor, IconButtonSize, UiIconButtonComponent } from '@isa/ui/buttons';
// import { within } from '@storybook/testing-library';
// import { expect } from '@storybook/jest';
import * as IsaIcons from '@isa/icons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
type UiIconButtonComponentInputs = {
color: IconButtonColor;
size: IconButtonSize;
pending: boolean;
disabled: boolean;
icon: string;
};
const meta: Meta<UiIconButtonComponentInputs> = {
component: UiIconButtonComponent,
decorators: [
moduleMetadata({
imports: [NgIconComponent],
providers: [provideIcons(IsaIcons)],
}),
],
title: 'ui/buttons/IconButton',
argTypes: {
icon: { control: 'select', options: Object.keys(IsaIcons) },
color: {
control: 'select',
options: ['brand', 'primary', 'secondary', 'tertiary'] as IconButtonColor[],
},
size: {
control: 'select',
options: ['small', 'medium'] as IconButtonSize[],
},
pending: {
control: 'boolean',
},
disabled: {
control: 'boolean',
},
},
args: {
icon: 'isaActionCheck',
},
render: (args) => ({
props: args,
template: `<ui-icon-button ${argsToTemplate(args, { exclude: ['disabled', 'icon'] })} ${args.disabled ? 'disabled' : ''}>
<ng-icon name="${args.icon}"></ng-icon>
</ui-icon-button>`,
}),
};
export default meta;
type Story = StoryObj<UiIconButtonComponent>;
export const Default: Story = {
args: {},
};

View File

@@ -0,0 +1,61 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { UiTextButtonComponent, TextButtonColor, TextButtonSize } from '@isa/ui/buttons';
// import { within } from '@storybook/testing-library';
// import { expect } from '@storybook/jest';
type UiTextButtonComponentInputs = {
color: TextButtonColor;
size: TextButtonSize;
disabled: boolean;
};
const meta: Meta<UiTextButtonComponentInputs> = {
component: UiTextButtonComponent,
title: 'ui/buttons/TextButton',
argTypes: {
color: {
control: 'select',
options: ['strong', 'subtle'] as TextButtonColor[],
},
size: {
control: 'select',
options: ['small', 'medium', 'large'] as TextButtonSize[],
},
disabled: {
control: 'boolean',
},
},
render: (args) => ({
props: args,
template: `<ui-text-button ${argsToTemplate(args, { exclude: ['disabled'] })} ${args.disabled ? 'disabled' : ''}>Button</ui-text-button>`,
}),
};
export default meta;
type Story = StoryObj<UiTextButtonComponent>;
export const Default: Story = {
args: {},
};
// export const Heading: Story = {
// args: {},
// play: async ({ canvasElement }) => {
// const canvas = within(canvasElement);
// expect(canvas.getByText(/ui-button works!/gi)).toBeTruthy();
// },
// };
export const StrongSmall: Story = {
args: {
color: 'strong',
size: 'small',
},
};
export const StrongLarge: Story = {
args: {
color: 'strong',
size: 'large',
},
};

View File

@@ -0,0 +1,24 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { TextFieldComponent } from '@isa/ui/input-controls';
const meta: Meta<TextFieldComponent> = {
component: TextFieldComponent,
title: 'ui/input-controls/TextField',
argTypes: {},
render: (args) => ({
props: args,
template: `
<ui-text-field>
<input type="text" placeholder="Enter your name" />
</ui-text-field>
`,
}),
};
export default meta;
type Story = StoryObj<TextFieldComponent>;
export const Default: Story = {
args: {},
};

View File

@@ -0,0 +1,61 @@
import { argsToTemplate, type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';
import { UiSearchBarClearComponent, UiSearchBarComponent } from '@isa/ui/search-bar';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionSearch } from '@isa/icons';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UiIconButtonComponent } from '@isa/ui/buttons';
export interface UiSearchBarComponentInputs {
placeholder: string;
value: string;
resetValue: string;
}
const meta: Meta<UiSearchBarComponentInputs> = {
component: UiSearchBarComponent,
decorators: [
moduleMetadata({
imports: [
UiIconButtonComponent,
UiSearchBarClearComponent,
NgIconComponent,
FormsModule,
ReactiveFormsModule,
],
providers: [provideIcons({ isaActionSearch })],
}),
],
title: 'ui/search-bar/SearchBar',
argTypes: {
placeholder: {
control: 'text',
},
value: {
control: 'text',
},
resetValue: {
control: 'text',
},
},
args: {
placeholder: 'Rechnungsnummer, E-Mail, Kundenkarte, Name...',
},
render: (args) => ({
props: { ...args, control: new FormControl(args.value) },
template: `<ui-search-bar ${argsToTemplate(args, { exclude: ['placeholder', 'value', 'resetValue'] })}>
<input [formControl]="control" type="text" uiSearchBarInput placeholder="${args.placeholder}" />
<ui-search-bar-clear value="${args.resetValue}"></ui-search-bar-clear>
<button type="submit" uiIconButton color="brand">
<ng-icon name="isaActionSearch"></ng-icon>
</button>
</ui-search-bar>`,
}),
};
export default meta;
type Story = StoryObj<UiSearchBarComponent>;
export const Primary: Story = {
args: {},
};

View File

@@ -6,5 +6,11 @@
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
"exclude": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"**/*.stories.ts",
"**/*.stories.js"
]
}

View File

@@ -2,5 +2,11 @@
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {},
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
"exclude": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"**/*.stories.ts",
"**/*.stories.js"
]
}

View File

@@ -22,6 +22,9 @@
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./.storybook/tsconfig.json"
}
],
"extends": "../../tsconfig.base.json",

View File

@@ -1,6 +1,6 @@
import nx from '@nx/eslint-plugin'
import prettierConfig from 'eslint-config-prettier'
import prettierPlugin from 'eslint-plugin-prettier'
import nx from '@nx/eslint-plugin';
import prettierConfig from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
export default [
...nx.configs['flat/base'],
@@ -39,4 +39,4 @@ export default [
},
...prettierConfig,
},
]
];

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,4 @@
export * from './lib/button.component';
export * from './lib/icon-button.component';
export * from './lib/text-button.component';
export * from './lib/types';

View File

@@ -1,5 +1,5 @@
@if (!pending()) {
<ng-content></ng-content>
} @else {
<ng-icon class="animate-spin" name="isaActionChevron"></ng-icon>
<ng-icon size="1.5rem" class="animate-spin text-isa-white" name="isaLoading"></ng-icon>
}

View File

@@ -1,11 +1,12 @@
.ui-button {
@apply font-sans cursor-pointer;
display: inline-flex;
justify-content: center;
align-items: center;
font-family: "Open Sans";
font-style: normal;
font-weight: 700;
border-radius: 6.25rem;
}
.ui-button__small {
@@ -36,21 +37,79 @@
.ui-button__primary {
@apply bg-isa-secondary-600 text-isa-white;
}
.ui-button__primary:hover {
@apply bg-isa-secondary-700;
}
&:hover {
@apply bg-isa-secondary-700;
}
&:active,
&.active {
@apply bg-isa-secondary-800;
}
.ui-button__primary:active,
.ui-button.active {
@apply bg-isa-secondary-800;
}
.ui-button__primary:disabled {
@apply bg-isa-neutral-400;
&:disabled,
&[disabled],
&.disabled {
@apply bg-isa-neutral-400;
}
}
.ui-button__secondary {
@apply border border-solid border-isa-secondary-600 text-isa-secondary-600;
&:hover {
@apply border-isa-secondary-700 bg-isa-neutral-100 text-isa-secondary-700;
}
&:active,
&.active {
@apply border-isa-secondary-800 bg-isa-neutral-200 text-isa-secondary-800;
}
&:disabled,
&[disabled],
&.disabled {
@apply border-isa-neutral-400 text-isa-neutral-400 bg-isa-white;
}
}
.ui-button__brand {
@apply bg-isa-accent-red text-isa-white;
&:hover {
@apply bg-isa-shades-red-600;
}
&:active,
&.active {
@apply bg-isa-shades-red-700;
}
&:disabled,
&[disabled],
&.disabled {
@apply bg-isa-neutral-400;
}
}
.ui-button__tertiary {
@apply bg-isa-neutral-300 text-isa-neutral-900;
&:hover {
@apply bg-isa-neutral-400;
}
&:active,
&.active {
@apply bg-isa-neutral-500 text-isa-white;
}
&:disabled,
&[disabled],
&.disabled {
@apply bg-isa-neutral-200 text-isa-neutral-500;
}
}
.ui-button:disabled,
.ui-button[disabled],
.ui-button.disabled {
@apply cursor-default;
}

View File

@@ -1,22 +1,30 @@
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { ButtonColor, ButtonSize } from './types';
import { isaActionChevron } from '@isa/icons';
import { isaLoading } from '@isa/icons';
@Component({
selector: 'ui-button, [uiButton]',
templateUrl: './button.component.html',
styleUrls: ['./button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [NgIconComponent],
// TODO: Gegen loader icon ersetzen
providers: [provideIcons({ isaActionChevron })],
providers: [provideIcons({ isaLoading })],
host: {
'[class]': '["ui-button", sizeClass(), colorClass()]',
'[tabindex]': 'tabIndex()',
},
})
export class NameComponent {
export class UiButtonComponent {
size = input<ButtonSize>('medium');
sizeClass = computed(() => `ui-button__${this.size()}`);
@@ -26,4 +34,6 @@ export class NameComponent {
colorClass = computed(() => `ui-button__${this.color()}`);
pending = input<boolean>(false);
tabIndex = input<number>(0);
}

View File

@@ -0,0 +1,5 @@
@if (!pending()) {
<ng-content select="ng-icon"></ng-content>
} @else {
<ng-icon class="animate-spin" name="isaLoading"></ng-icon>
}

View File

@@ -0,0 +1,98 @@
.ui-icon-button {
display: inline-flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
@apply rounded-full cursor-pointer bg-isa-white;
}
.ui-icon-button__small {
height: 2.5rem;
width: 2.5rem;
padding: 0.5rem;
@apply text-xl;
}
.ui-icon-button__medium {
padding: 0.75rem;
@apply text-2xl;
}
.ui-icon-button__primary {
@apply bg-isa-secondary-600 text-isa-white;
&:hover {
@apply bg-isa-secondary-700;
}
&:active,
&.active {
@apply bg-isa-secondary-800;
}
&:disabled,
&[disabled],
&.disabled {
@apply bg-isa-neutral-400;
}
}
.ui-icon-button__secondary {
@apply border border-solid border-isa-secondary-600 text-isa-secondary-600;
&:hover {
@apply border-isa-secondary-700 bg-isa-neutral-100 text-isa-secondary-700;
}
&:active,
&.active {
@apply border-isa-secondary-800 bg-isa-neutral-200 text-isa-secondary-800;
}
&:disabled,
&[disabled],
&.disabled {
@apply border-isa-neutral-400 text-isa-neutral-400 bg-isa-white;
}
}
.ui-icon-button__brand {
@apply bg-isa-accent-red text-isa-white;
&:hover {
@apply bg-isa-shades-red-600;
}
&:active,
&.active {
@apply bg-isa-shades-red-700;
}
&:disabled,
&[disabled],
&.disabled {
@apply bg-isa-neutral-400;
}
}
.ui-icon-button__tertiary {
@apply bg-isa-neutral-300 text-isa-neutral-900;
&:hover {
@apply bg-isa-neutral-400;
}
&:active,
&.active {
@apply bg-isa-neutral-500 text-isa-white;
}
&:disabled,
&[disabled],
&.disabled {
@apply bg-isa-neutral-200 text-isa-neutral-500;
}
}
.ui-icon-button:disabled,
.ui-icon-button[disabled],
.ui-icon-button.disabled {
@apply cursor-default;
}

View File

@@ -0,0 +1,38 @@
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import { IconButtonColor, IconButtonSize } from './types';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaLoading } from '@isa/icons';
@Component({
selector: 'ui-icon-button, [uiIconButton]',
templateUrl: './icon-button.component.html',
styleUrls: ['./icon-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [NgIconComponent],
providers: [provideIcons({ isaLoading })],
host: {
'[class]': "['ui-icon-button', sizeClass(), colorClass()]",
'[tabindex]': 'tabIndex()',
},
})
export class UiIconButtonComponent {
size = input<IconButtonSize>('medium');
sizeClass = computed(() => `ui-icon-button__${this.size()}`);
color = input<IconButtonColor>('primary');
colorClass = computed(() => `ui-icon-button__${this.color()}`);
pending = input<boolean>(false);
tabIndex = input<number>(0);
}

View File

@@ -0,0 +1 @@
<ng-content></ng-content>

View File

@@ -0,0 +1,55 @@
.ui-text-button {
@apply font-sans;
display: inline-flex;
justify-content: center;
align-items: center;
text-align: center;
font-style: normal;
font-weight: 700;
cursor: pointer;
}
.ui-text-button__strong {
@apply text-isa-accent-blue;
&:hover {
@apply text-isa-secondary-500;
}
}
.ui-text-button__subtle {
@apply text-isa-neutral-900;
text-decoration-line: underline;
text-decoration-style: solid;
text-decoration-skip-ink: none;
text-decoration-thickness: auto;
text-underline-offset: auto;
text-underline-position: from-font;
&:hover {
@apply text-isa-neutral-700;
}
}
.ui-text-button:disabled,
.ui-text-button[disabled],
.ui-text-button.disabled {
@apply text-isa-neutral-400 cursor-default;
}
.ui-text-button__small {
font-size: 0.75rem;
line-height: 1rem; /* 133.333% */
}
.ui-text-button__medium {
font-size: 0.875rem;
line-height: 1.25rem; /* 142.857% */
}
.ui-text-button__large {
font-size: 1rem;
line-height: 1.5rem; /* 150% */
}

View File

@@ -0,0 +1,33 @@
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import { TextButtonColor, TextButtonSize } from './types';
@Component({
selector: 'ui-text-button, [uiTextButton]',
templateUrl: './text-button.component.html',
styleUrls: ['./text-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [],
host: {
'[class]': '["ui-text-button", sizeClass(), colorClass()]',
'[tabindex]': 'tabIndex()',
},
})
export class UiTextButtonComponent {
size = input<TextButtonSize>('medium');
sizeClass = computed(() => `ui-text-button__${this.size()}`);
color = input<TextButtonColor>('strong');
colorClass = computed(() => `ui-text-button__${this.color()}`);
tabIndex = input<number>(0);
}

View File

@@ -14,3 +14,25 @@ export const ButtonColor = {
} as const;
export type ButtonColor = (typeof ButtonColor)[keyof typeof ButtonColor];
export const TextButtonSize = ButtonSize;
export type TextButtonSize = (typeof TextButtonSize)[keyof typeof TextButtonSize];
export const TextButtonColor = {
Strong: 'strong',
Subtle: 'subtle',
} as const;
export type TextButtonColor = (typeof TextButtonColor)[keyof typeof TextButtonColor];
export const IconButtonSize = {
Small: 'small',
Medium: 'medium',
} as const;
export type IconButtonSize = (typeof IconButtonSize)[keyof typeof IconButtonSize];
export const IconButtonColor = ButtonColor;
export type IconButtonColor = (typeof IconButtonColor)[keyof typeof IconButtonColor];

View File

@@ -1 +1 @@
export * from './lib/ui-input-controls/ui-input-controls.component';
export * from './lib/text-field/text-field.component';

View File

@@ -0,0 +1,3 @@
<div class="ui-text-field__wrapper">
<ng-content></ng-content>
</div>

View File

@@ -0,0 +1,44 @@
.ui-text-field {
display: inline-flex;
align-items: center;
gap: 1rem;
border-radius: 6.25rem;
height: 3.5rem;
@apply bg-isa-white;
@apply border border-solid border-isa-neutral-600;
@apply focus-within:border-isa-neutral-900;
&:has(input.ng-invalid) {
@apply border-isa-accent-red;
}
.ui-text-field__wrapper {
display: flex;
width: 22.875rem;
height: 2.5rem;
min-width: 10rem;
padding: 0.5rem 1.25rem;
justify-content: space-between;
align-items: center;
}
input[type="text"] {
@apply focus:outline-none;
@apply text-isa-neutral-900 appearance-none;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
&::placeholder {
@apply text-isa-neutral-400;
}
&:hover::placeholder,
&:focus::placeholder {
@apply text-isa-neutral-900;
}
}
}

View File

@@ -0,0 +1,15 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'ui-text-field',
templateUrl: './text-field.component.html',
styleUrls: ['./text-field.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [],
host: {
'[class]': '["ui-text-field"]',
},
})
export class TextFieldComponent {}

View File

@@ -1 +0,0 @@
<p>UiInputControls works!</p>

View File

@@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UiInputControlsComponent } from './ui-input-controls.component';
describe('UiInputControlsComponent', () => {
let component: UiInputControlsComponent;
let fixture: ComponentFixture<UiInputControlsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UiInputControlsComponent],
}).compileComponents();
fixture = TestBed.createComponent(UiInputControlsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,10 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'ui-ui-input-controls',
imports: [CommonModule],
templateUrl: './ui-input-controls.component.html',
styleUrl: './ui-input-controls.component.css',
})
export class UiInputControlsComponent {}

View File

@@ -26,9 +26,4 @@ export default [
],
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {},
},
];

View File

@@ -1 +1,2 @@
export * from './lib/ui-search-bar/ui-search-bar.component';
export * from './lib/search-bar-clear.component';
export * from './lib/search-bar.component';

View File

@@ -0,0 +1,38 @@
import {
ChangeDetectionStrategy,
Component,
inject,
input,
ViewEncapsulation,
} from '@angular/core';
import { isaActionClose } from '@isa/icons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { UiSearchBarComponent } from './search-bar.component';
import { asapScheduler } from 'rxjs';
@Component({
selector: 'ui-search-bar-clear',
template: '<ng-icon name="isaActionClose"></ng-icon>',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [NgIconComponent],
providers: [provideIcons({ isaActionClose })],
host: {
'[class]': "['ui-search-bar__action__close']",
'(click)': 'reset(null)',
},
})
export class UiSearchBarClearComponent {
private searchBar = inject(UiSearchBarComponent);
value = input<unknown>();
reset(value?: unknown): void {
const resetValue = value ?? this.value();
this.searchBar.inputControl().reset(resetValue);
asapScheduler.schedule(() => {
this.searchBar.inputControlElementRef().nativeElement.focus();
});
}
}

View File

@@ -0,0 +1,8 @@
<ng-content select="[uiSearchBarInput]"></ng-content>
@let control = inputControl();
<div class="ui-search-bar__actions">
<ng-content select="ui-search-bar-clear"></ng-content>
<ng-content select="[uiIconButton]"></ng-content>
</div>

View File

@@ -0,0 +1,58 @@
.ui-search-bar {
display: flex;
width: 32.5rem;
height: 3.75rem;
padding: 0rem 0.5rem 0rem 1.5rem;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
@apply bg-isa-white rounded-full;
.ui-search-bar__action__close {
display: inline-flex;
}
&:has(input[type="text"]:placeholder-shown) {
.ui-search-bar__action__close {
display: none;
}
}
}
input[type="text"] {
appearance: none;
flex-grow: 1;
font-size: 0.875rem;
font-style: normal;
font-weight: 700;
line-height: 1.25rem; /* 142.857% */
@apply text-isa-neutral-900;
&::placeholder {
@apply text-isa-neutral-500;
font-size: 0.875rem;
font-style: normal;
font-weight: 700;
line-height: 1.25rem; /* 142.857% */
&:hover,
&:focus {
@apply text-isa-neutral-900;
}
}
@apply focus:outline-none;
}
.ui-search-bar__actions {
@apply flex items-center gap-4 shrink-0;
}
.ui-search-bar__action__close {
display: inline-flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
@apply text-2xl cursor-pointer;
}

View File

@@ -0,0 +1,28 @@
import {
ChangeDetectionStrategy,
Component,
contentChild,
ElementRef,
ViewEncapsulation,
} from '@angular/core';
import { isaActionClose } from '@isa/icons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { NgControl } from '@angular/forms';
@Component({
selector: 'ui-search-bar',
imports: [NgIconComponent],
templateUrl: './search-bar.component.html',
styleUrl: './search-bar.component.scss',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideIcons({ isaActionClose })],
host: {
'[class]': '["ui-search-bar"]',
},
})
export class UiSearchBarComponent {
inputControl = contentChild.required(NgControl, { read: NgControl });
inputControlElementRef = contentChild.required(NgControl, { read: ElementRef });
}

View File

@@ -1 +0,0 @@
<p>UiSearchBar works!</p>

View File

@@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UiSearchBarComponent } from './ui-search-bar.component';
describe('UiSearchBarComponent', () => {
let component: UiSearchBarComponent;
let fixture: ComponentFixture<UiSearchBarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UiSearchBarComponent],
}).compileComponents();
fixture = TestBed.createComponent(UiSearchBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,10 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'ui-ui-search-bar',
imports: [CommonModule],
templateUrl: './ui-search-bar.component.html',
styleUrl: './ui-search-bar.component.css',
})
export class UiSearchBarComponent {}

View File

@@ -44,6 +44,9 @@
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"build-storybook": {
"cache": true
}
},
"defaultBase": "develop",
@@ -59,7 +62,10 @@
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/src/test-setup.[jt]s",
"!{projectRoot}/test-setup.[jt]s"
"!{projectRoot}/test-setup.[jt]s",
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
"!{projectRoot}/.storybook/**/*",
"!{projectRoot}/tsconfig.storybook.json"
]
},
"generators": {

3639
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -75,6 +75,13 @@
"@schematics/angular": "^19.1.8",
"@softarc/eslint-plugin-sheriff": "^0.18.0",
"@softarc/sheriff-core": "^0.18.0",
"@storybook/addon-essentials": "^8.4.6",
"@storybook/addon-interactions": "^8.4.6",
"@storybook/angular": "^8.4.6",
"@storybook/core-server": "^8.4.6",
"@storybook/jest": "^0.2.3",
"@storybook/test-runner": "^0.19.0",
"@storybook/testing-library": "^0.2.2",
"@swc-node/register": "~1.9.1",
"@swc/core": "~1.5.7",
"@swc/helpers": "~0.5.11",

View File

@@ -1,4 +1,4 @@
const plugin = require('tailwindcss/plugin')
const plugin = require('tailwindcss/plugin');
/** @type {import('tailwindcss').Config} */
module.exports = {
@@ -83,6 +83,7 @@ module.exports = {
white: '#FFFFFF',
accent: {
red: '#DF001B',
blue: '#354ACB',
},
shades: {
red: {
@@ -245,7 +246,7 @@ module.exports = {
require('./tailwind-plugins/section.plugin.js'),
require('./tailwind-plugins/typography.plugin.js'),
plugin(({ addVariant }) => {
addVariant('open', '&.open')
addVariant('open', '&.open');
}),
],
}
};

View File

@@ -60,6 +60,7 @@
"@ui/*": ["apps/isa-app/src/ui/*/index.ts"],
"@utils/*": ["apps/isa-app/src/utils/*/index.ts"],
"packageJson": ["package.json"]
}
},
"skipLibCheck": true
}
}

View File

@@ -9,5 +9,10 @@
"angularCompilerOptions": {
"strictTemplates": true
},
"extends": "./tsconfig.base.json"
"extends": "./tsconfig.base.json",
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
}
}