Skip to content

Commit 69b51a2

Browse files
committed
feat: add input component (spartanui)
1 parent 0f0952d commit 69b51a2

File tree

11 files changed

+281
-0
lines changed

11 files changed

+281
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
22

3+
# Examples
4+
/old-ui-pipe-examples-code
5+
36
# Compiled output
47
/dist
58
/tmp

libs/components/ui/input/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# input
2+
3+
This library was generated with [Nx](https://nx.dev).
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import nx from "@nx/eslint-plugin";
2+
import baseConfig from "../../../../eslint.base.config.mjs";
3+
4+
export default [
5+
...baseConfig,
6+
{
7+
files: [
8+
"**/*.json"
9+
],
10+
rules: {
11+
"@nx/dependency-checks": [
12+
"error",
13+
{
14+
ignoredFiles: [
15+
"{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}"
16+
]
17+
}
18+
]
19+
},
20+
languageOptions: {
21+
parser: await import("jsonc-eslint-parser")
22+
}
23+
},
24+
...nx.configs["flat/angular"],
25+
...nx.configs["flat/angular-template"],
26+
{
27+
files: [
28+
"**/*.ts"
29+
],
30+
rules: {
31+
"@angular-eslint/directive-selector": [
32+
"error",
33+
{
34+
type: "attribute",
35+
prefix: "hlm",
36+
style: "camelCase"
37+
}
38+
],
39+
"@angular-eslint/component-selector": [
40+
"error",
41+
{
42+
type: "element",
43+
prefix: "hlm",
44+
style: "kebab-case"
45+
}
46+
],
47+
"@angular-eslint/no-input-rename": "off",
48+
"@nx/enforce-module-boundaries":
49+
(() => {
50+
const r = baseConfig.find(c => c.rules && c.rules["@nx/enforce-module-boundaries"])?.rules["@nx/enforce-module-boundaries"];
51+
return r ? [r[0], { ...r[1], allowCircularSelfDependency: true }] : undefined;
52+
})(),
53+
"@angular-eslint/directive-class-suffix": "off",
54+
"@angular-eslint/component-class-suffix": "off",
55+
"@typescript-eslint/naming-convention": [
56+
"error",
57+
{
58+
"selector": "classProperty",
59+
"modifiers": ["protected"],
60+
"format": ["camelCase"],
61+
"leadingUnderscore": "require"
62+
}
63+
]
64+
}
65+
},
66+
{
67+
files: [
68+
"**/*.html"
69+
],
70+
// Override or add rules here
71+
rules: {
72+
"@angular-eslint/template/interactive-supports-focus": "off",
73+
"@angular-eslint/template/click-events-have-key-events": "off"
74+
}
75+
}
76+
];
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
3+
"dest": "../../../../dist/libs/components/ui/input",
4+
"lib": {
5+
"entryFile": "src/index.ts"
6+
}
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@spartan-ng/helm/input",
3+
"version": "0.0.1",
4+
"peerDependencies": {
5+
"@angular/common": "^21.0.0",
6+
"@angular/core": "^21.0.0"
7+
},
8+
"sideEffects": false
9+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "input",
3+
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/components/ui/input/src",
5+
"prefix": "hlm",
6+
"projectType": "library",
7+
"tags": [],
8+
"targets": {
9+
"build": {
10+
"executor": "@nx/angular:ng-packagr-lite",
11+
"outputs": [
12+
"{workspaceRoot}/dist/{projectRoot}"
13+
],
14+
"options": {
15+
"project": "libs/components/ui/input/ng-package.json",
16+
"tsConfig": "libs/components/ui/input/tsconfig.lib.json"
17+
},
18+
"configurations": {
19+
"production": {
20+
"tsConfig": "libs/components/ui/input/tsconfig.lib.prod.json"
21+
},
22+
"development": {}
23+
},
24+
"defaultConfiguration": "production"
25+
},
26+
"lint": {
27+
"executor": "@nx/eslint:lint"
28+
}
29+
}
30+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { HlmInput } from './lib/hlm-input';
2+
3+
export * from './lib/hlm-input';
4+
5+
export const HlmInputImports = [HlmInput] as const;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {
2+
computed,
3+
Directive,
4+
effect,
5+
forwardRef,
6+
inject,
7+
Injector,
8+
input,
9+
linkedSignal,
10+
signal,
11+
untracked,
12+
type DoCheck,
13+
} from '@angular/core';
14+
import { FormGroupDirective, NgControl, NgForm } from '@angular/forms';
15+
import { BrnFormFieldControl } from '@spartan-ng/brain/form-field';
16+
import { ErrorStateMatcher, ErrorStateTracker } from '@spartan-ng/brain/forms';
17+
import { classes } from '@spartan-ng/helm/utils';
18+
import { cva, type VariantProps } from 'class-variance-authority';
19+
import type { ClassValue } from 'clsx';
20+
21+
export const inputVariants = cva(
22+
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-[3px] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
23+
{
24+
variants: {
25+
error: {
26+
auto: '[&.ng-invalid.ng-touched]:border-destructive [&.ng-invalid.ng-touched]:ring-destructive/20 dark:[&.ng-invalid.ng-touched]:ring-destructive/40',
27+
true: 'border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
28+
},
29+
},
30+
defaultVariants: {
31+
error: 'auto',
32+
},
33+
},
34+
);
35+
type InputVariants = VariantProps<typeof inputVariants>;
36+
37+
@Directive({
38+
selector: '[hlmInput]',
39+
providers: [
40+
{
41+
provide: BrnFormFieldControl,
42+
useExisting: forwardRef(() => HlmInput),
43+
},
44+
],
45+
})
46+
export class HlmInput implements BrnFormFieldControl, DoCheck {
47+
private readonly _injector = inject(Injector);
48+
private readonly _additionalClasses = signal<ClassValue>('');
49+
50+
private readonly _errorStateTracker: ErrorStateTracker;
51+
52+
private readonly _defaultErrorStateMatcher = inject(ErrorStateMatcher);
53+
private readonly _parentForm = inject(NgForm, { optional: true });
54+
private readonly _parentFormGroup = inject(FormGroupDirective, { optional: true });
55+
56+
public readonly error = input<InputVariants['error']>('auto');
57+
58+
protected readonly _state = linkedSignal(() => ({ error: this.error() }));
59+
60+
public readonly ngControl: NgControl | null = this._injector.get(NgControl, null);
61+
62+
public readonly errorState = computed(() => this._errorStateTracker.errorState());
63+
64+
constructor() {
65+
this._errorStateTracker = new ErrorStateTracker(
66+
this._defaultErrorStateMatcher,
67+
this.ngControl,
68+
this._parentFormGroup,
69+
this._parentForm,
70+
);
71+
72+
classes(() => [inputVariants({ error: this._state().error }), this._additionalClasses()]);
73+
74+
effect(() => {
75+
const error = this._errorStateTracker.errorState();
76+
untracked(() => {
77+
if (this.ngControl) {
78+
const shouldShowError = error && this.ngControl.invalid && (this.ngControl.touched || this.ngControl.dirty);
79+
this._errorStateTracker.errorState.set(shouldShowError ? true : false);
80+
this.setError(shouldShowError ? true : 'auto');
81+
}
82+
});
83+
});
84+
}
85+
86+
ngDoCheck() {
87+
this._errorStateTracker.updateErrorState();
88+
}
89+
90+
setError(error: InputVariants['error']) {
91+
this._state.set({ error });
92+
}
93+
94+
setClass(classes: string): void {
95+
this._additionalClasses.set(classes);
96+
}
97+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"extends": "../../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"isolatedModules": true,
5+
"moduleResolution": "bundler",
6+
"strict": true,
7+
"noImplicitOverride": true,
8+
"noPropertyAccessFromIndexSignature": true,
9+
"noImplicitReturns": true,
10+
"noFallthroughCasesInSwitch": true,
11+
"emitDecoratorMetadata": false,
12+
"module": "preserve"
13+
},
14+
"angularCompilerOptions": {
15+
"enableI18nLegacyMessageIdFormat": false,
16+
"strictInjectionParameters": true,
17+
"strictInputAccessModifiers": true,
18+
"strictTemplates": true
19+
},
20+
"files": [],
21+
"include": [],
22+
"references": [
23+
{
24+
"path": "./tsconfig.lib.json"
25+
}
26+
]
27+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "../../../../dist/out-tsc",
5+
"declaration": true,
6+
"declarationMap": true,
7+
"inlineSources": true,
8+
"types": []
9+
},
10+
"include": [
11+
"src/**/*.ts"
12+
],
13+
"exclude": [
14+
"src/**/*.spec.ts",
15+
"src/**/*.test.ts"
16+
]
17+
}

0 commit comments

Comments
 (0)