diff --git a/projects/qd-design-demo/src/app/app.html b/projects/qd-design-demo/src/app/app.html
index a03ed8c..610f30e 100644
--- a/projects/qd-design-demo/src/app/app.html
+++ b/projects/qd-design-demo/src/app/app.html
@@ -80,6 +80,7 @@
+
diff --git a/projects/qd-design-demo/src/app/app.ts b/projects/qd-design-demo/src/app/app.ts
index 3444320..f2ad1d5 100644
--- a/projects/qd-design-demo/src/app/app.ts
+++ b/projects/qd-design-demo/src/app/app.ts
@@ -3,6 +3,7 @@ import {QdButton} from '../../../qd-design/src/lib/qd-button/qd-button';
import {QdIconButton} from '../../../qd-design/src/lib/qd-icon-button/qd-icon-button';
import {QdInputText} from '../../../qd-design/src/lib/qd-input-text/qd-input-text';
import {QdInputPassword} from '../../../qd-design/src/lib/qd-input-password/qd-input-password';
+import {QdInputFile} from '../../../qd-design/src/lib/qd-input-file/qd-input-file';
import {QdCard} from '../../../qd-design/src/lib/qd-card/qd-card';
import {NgOptimizedImage} from '@angular/common';
import {QdTooltipDirective} from '../../../qd-design/src/directives/qd-tooltip-directive';
@@ -11,14 +12,26 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
@Component({
selector: 'app-root',
- imports: [QdButton, QdIconButton, QdCard, NgOptimizedImage, QdTooltipDirective, QdInputText, QdInputPassword, FormField, FormsModule, ReactiveFormsModule],
+ imports: [
+ QdButton,
+ QdIconButton,
+ QdCard,
+ NgOptimizedImage,
+ QdTooltipDirective,
+ QdInputText,
+ QdInputPassword,
+ QdInputFile,
+ FormField,
+ FormsModule,
+ ReactiveFormsModule
+ ],
templateUrl: './app.html',
standalone: true,
styleUrl: './app.scss'
})
export class App {
protected readonly title = signal('qd-design-demo');
- loginModel = signal({checkEmail: true, email: '', password: ''});
+ loginModel = signal({checkEmail: true, email: '', password: '', file: new File([], "", undefined)});
loginForm = form(this.loginModel, (schemaPath) => {
required(schemaPath.email, {message: 'Email is required'});
email(schemaPath.email, {message: 'Enter a valid email address'});
@@ -34,5 +47,6 @@ export class App {
console.log(this.loginModel().email);
console.log(this.loginModel().checkEmail);
console.log(this.loginModel().password);
+ console.log(this.loginModel().file.bytes());
}
}
diff --git a/projects/qd-design/src/lib/qd-input-file/qd-input-file.css b/projects/qd-design/src/lib/qd-input-file/qd-input-file.css
new file mode 100644
index 0000000..3ba38b4
--- /dev/null
+++ b/projects/qd-design/src/lib/qd-input-file/qd-input-file.css
@@ -0,0 +1,427 @@
+@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
+@import url("https://fonts.googleapis.com/css?family=Roboto");
+:root {
+ --font-family-title: Roboto, sans-serif;
+ --font-family-content: Roboto, sans-serif;
+ /* display - large */
+ --display-large-font-size: 57px;
+ --display-large-font-weight: 400px;
+ --display-large-letter-spacing: -0.25px;
+ --display-large-line-height: 64px;
+ /* display - medium */
+ --display-medium-font-size: 45px;
+ --display-medium-font-weight: 400px;
+ --display-medium-letter-spacing: 0px;
+ --display-medium-line-height: 52px;
+ /* display - small */
+ --display-small-font-size: 36px;
+ --display-small-font-weight: 400px;
+ --display-small-letter-spacing: 0px;
+ --display-small-line-height: 44px;
+ /* headline - large */
+ --headline-large-font-size: 32px;
+ --headline-large-font-weight: 400px;
+ --headline-large-letter-spacing: 0px;
+ --headline-large-line-height: 40px;
+ /* headline - medium */
+ --headline-medium-font-size: 28px;
+ --headline-medium-font-weight: 400px;
+ --headline-medium-letter-spacing: 0px;
+ --headline-medium-line-height: 36px;
+ /* headline - small */
+ --headline-small-font-size: 24px;
+ --headline-small-font-weight: 400px;
+ --headline-small-letter-spacing: 0px;
+ --headline-small-line-height: 32px;
+ /* title - large */
+ --title-large-font-size: 22px;
+ --title-large-font-weight: 400px;
+ --title-large-letter-spacing: 0px;
+ --title-large-line-height: 28px;
+ /* title - medium */
+ --title-medium-font-size: 18px;
+ --title-medium-font-weight: 500px;
+ --title-medium-letter-spacing: 0.15px;
+ --title-medium-line-height: 26px;
+ /* title - small */
+ --title-small-font-size: 16px;
+ --title-small-font-weight: 500px;
+ --title-small-letter-spacing: 0.10px;
+ --title-small-line-height: 24px;
+ /* body - large */
+ --body-large-font-size: 18px;
+ --body-large-font-weight: 400px;
+ --body-large-letter-spacing: 0.50px;
+ --body-large-line-height: 26px;
+ /* body - medium */
+ --body-medium-font-size: 16px;
+ --body-medium-font-weight: 400px;
+ --body-medium-letter-spacing: 0.25px;
+ --body-medium-line-height: 24px;
+ /* body - small */
+ --body-small-font-size: 14px;
+ --body-small-font-weight: 400px;
+ --body-small-letter-spacing: 0.40px;
+ --body-small-line-height: 20px;
+ /* label - large */
+ --label-large-font-size: 14px;
+ --label-large-font-weight: 500px;
+ --label-large-letter-spacing: 0.10px;
+ --label-large-line-height: 20px;
+ /* label - medium */
+ --label-medium-font-size: 12px;
+ --label-medium-font-weight: 500px;
+ --label-medium-letter-spacing: 0.50px;
+ --label-medium-line-height: 16px;
+ /* label - small */
+ --label-small-font-size: 11px;
+ --label-small-font-weight: 500px;
+ --label-small-letter-spacing: 0.50px;
+ --label-small-line-height: 16px;
+}
+
+.scrollable-y {
+ max-height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.height-100 {
+ height: 100%;
+ max-height: 100%;
+ min-height: 100%;
+}
+
+.height-100-vh {
+ height: 100vh;
+ max-height: 100vh;
+ min-height: 100vh;
+}
+
+.width-100 {
+ max-width: 100%;
+ min-width: 100%;
+ width: 100%;
+}
+
+.width-100-vw {
+ max-width: 100vw;
+ min-width: 100vw;
+ width: 100vw;
+}
+
+body {
+ --md-sys-color-primary: rgb(73 93 146);
+ --md-sys-color-surface-tint: rgb(73 93 146);
+ --md-sys-color-on-primary: rgb(255 255 255);
+ --md-sys-color-primary-container: rgb(218 225 255);
+ --md-sys-color-on-primary-container: rgb(49 69 120);
+ --md-sys-color-secondary: rgb(88 94 113);
+ --md-sys-color-on-secondary: rgb(255 255 255);
+ --md-sys-color-secondary-container: rgb(221 226 249);
+ --md-sys-color-on-secondary-container: rgb(65 70 89);
+ --md-sys-color-tertiary: rgb(115 84 113);
+ --md-sys-color-on-tertiary: rgb(255 255 255);
+ --md-sys-color-tertiary-container: rgb(255 214 249);
+ --md-sys-color-on-tertiary-container: rgb(90 61 88);
+ --md-sys-color-error: rgb(186 26 26);
+ --md-sys-color-on-error: rgb(255 255 255);
+ --md-sys-color-error-container: rgb(255 218 214);
+ --md-sys-color-on-error-container: rgb(147 0 10);
+ --md-sys-color-background: rgb(250 248 255);
+ --md-sys-color-on-background: rgb(26 27 33);
+ --md-sys-color-surface: rgb(250 248 255);
+ --md-sys-color-on-surface: rgb(26 27 33);
+ --md-sys-color-surface-variant: rgb(225 226 236);
+ --md-sys-color-on-surface-variant: rgb(69 70 79);
+ --md-sys-color-outline: rgb(117 118 128);
+ --md-sys-color-outline-variant: rgb(197 198 208);
+ --md-sys-color-shadow: rgb(0 0 0);
+ --md-sys-color-scrim: rgb(0 0 0);
+ --md-sys-color-inverse-surface: rgb(47 48 54);
+ --md-sys-color-inverse-on-surface: rgb(241 240 247);
+ --md-sys-color-inverse-primary: rgb(179 197 255);
+ --md-sys-color-primary-fixed: rgb(218 225 255);
+ --md-sys-color-on-primary-fixed: rgb(0 24 73);
+ --md-sys-color-primary-fixed-dim: rgb(179 197 255);
+ --md-sys-color-on-primary-fixed-variant: rgb(49 69 120);
+ --md-sys-color-secondary-fixed: rgb(221 226 249);
+ --md-sys-color-on-secondary-fixed: rgb(21 27 44);
+ --md-sys-color-secondary-fixed-dim: rgb(193 198 221);
+ --md-sys-color-on-secondary-fixed-variant: rgb(65 70 89);
+ --md-sys-color-tertiary-fixed: rgb(255 214 249);
+ --md-sys-color-on-tertiary-fixed: rgb(43 18 43);
+ --md-sys-color-tertiary-fixed-dim: rgb(225 187 220);
+ --md-sys-color-on-tertiary-fixed-variant: rgb(90 61 88);
+ --md-sys-color-surface-dim: rgb(218 217 224);
+ --md-sys-color-surface-bright: rgb(250 248 255);
+ --md-sys-color-surface-container-lowest: rgb(255 255 255);
+ --md-sys-color-surface-container-low: rgb(244 243 250);
+ --md-sys-color-surface-container: rgb(238 237 244);
+ --md-sys-color-surface-container-high: rgb(232 231 239);
+ --md-sys-color-surface-container-highest: rgb(227 226 233);
+ transition: background-color 0.33s, color 0.33s;
+}
+
+@media (prefers-color-scheme: dark) {
+ body:not(.light-theme) {
+ --md-sys-color-primary: rgb(179 197 255);
+ --md-sys-color-surface-tint: rgb(179 197 255);
+ --md-sys-color-on-primary: rgb(25 46 96);
+ --md-sys-color-primary-container: rgb(49 69 120);
+ --md-sys-color-on-primary-container: rgb(218 225 255);
+ --md-sys-color-secondary: rgb(193 198 221);
+ --md-sys-color-on-secondary: rgb(42 48 66);
+ --md-sys-color-secondary-container: rgb(65 70 89);
+ --md-sys-color-on-secondary-container: rgb(221 226 249);
+ --md-sys-color-tertiary: rgb(225 187 220);
+ --md-sys-color-on-tertiary: rgb(66 39 65);
+ --md-sys-color-tertiary-container: rgb(90 61 88);
+ --md-sys-color-on-tertiary-container: rgb(255 214 249);
+ --md-sys-color-error: rgb(255 180 171);
+ --md-sys-color-on-error: rgb(105 0 5);
+ --md-sys-color-error-container: rgb(147 0 10);
+ --md-sys-color-on-error-container: rgb(255 218 214);
+ --md-sys-color-background: rgb(18 19 24);
+ --md-sys-color-on-background: rgb(227 226 233);
+ --md-sys-color-surface: rgb(18 19 24);
+ --md-sys-color-on-surface: rgb(227 226 233);
+ --md-sys-color-surface-variant: rgb(69 70 79);
+ --md-sys-color-on-surface-variant: rgb(197 198 208);
+ --md-sys-color-outline: rgb(143 144 154);
+ --md-sys-color-outline-variant: rgb(69 70 79);
+ --md-sys-color-shadow: rgb(0 0 0);
+ --md-sys-color-scrim: rgb(0 0 0);
+ --md-sys-color-inverse-surface: rgb(227 226 233);
+ --md-sys-color-inverse-on-surface: rgb(47 48 54);
+ --md-sys-color-inverse-primary: rgb(73 93 146);
+ --md-sys-color-primary-fixed: rgb(218 225 255);
+ --md-sys-color-on-primary-fixed: rgb(0 24 73);
+ --md-sys-color-primary-fixed-dim: rgb(179 197 255);
+ --md-sys-color-on-primary-fixed-variant: rgb(49 69 120);
+ --md-sys-color-secondary-fixed: rgb(221 226 249);
+ --md-sys-color-on-secondary-fixed: rgb(21 27 44);
+ --md-sys-color-secondary-fixed-dim: rgb(193 198 221);
+ --md-sys-color-on-secondary-fixed-variant: rgb(65 70 89);
+ --md-sys-color-tertiary-fixed: rgb(255 214 249);
+ --md-sys-color-on-tertiary-fixed: rgb(43 18 43);
+ --md-sys-color-tertiary-fixed-dim: rgb(225 187 220);
+ --md-sys-color-on-tertiary-fixed-variant: rgb(90 61 88);
+ --md-sys-color-surface-dim: rgb(18 19 24);
+ --md-sys-color-surface-bright: rgb(56 57 63);
+ --md-sys-color-surface-container-lowest: rgb(13 14 19);
+ --md-sys-color-surface-container-low: rgb(26 27 33);
+ --md-sys-color-surface-container: rgb(30 31 37);
+ --md-sys-color-surface-container-high: rgb(41 42 47);
+ --md-sys-color-surface-container-highest: rgb(51 52 58);
+ }
+}
+body.dark-theme {
+ --md-sys-color-primary: rgb(179 197 255);
+ --md-sys-color-surface-tint: rgb(179 197 255);
+ --md-sys-color-on-primary: rgb(25 46 96);
+ --md-sys-color-primary-container: rgb(49 69 120);
+ --md-sys-color-on-primary-container: rgb(218 225 255);
+ --md-sys-color-secondary: rgb(193 198 221);
+ --md-sys-color-on-secondary: rgb(42 48 66);
+ --md-sys-color-secondary-container: rgb(65 70 89);
+ --md-sys-color-on-secondary-container: rgb(221 226 249);
+ --md-sys-color-tertiary: rgb(225 187 220);
+ --md-sys-color-on-tertiary: rgb(66 39 65);
+ --md-sys-color-tertiary-container: rgb(90 61 88);
+ --md-sys-color-on-tertiary-container: rgb(255 214 249);
+ --md-sys-color-error: rgb(255 180 171);
+ --md-sys-color-on-error: rgb(105 0 5);
+ --md-sys-color-error-container: rgb(147 0 10);
+ --md-sys-color-on-error-container: rgb(255 218 214);
+ --md-sys-color-background: rgb(18 19 24);
+ --md-sys-color-on-background: rgb(227 226 233);
+ --md-sys-color-surface: rgb(18 19 24);
+ --md-sys-color-on-surface: rgb(227 226 233);
+ --md-sys-color-surface-variant: rgb(69 70 79);
+ --md-sys-color-on-surface-variant: rgb(197 198 208);
+ --md-sys-color-outline: rgb(143 144 154);
+ --md-sys-color-outline-variant: rgb(69 70 79);
+ --md-sys-color-shadow: rgb(0 0 0);
+ --md-sys-color-scrim: rgb(0 0 0);
+ --md-sys-color-inverse-surface: rgb(227 226 233);
+ --md-sys-color-inverse-on-surface: rgb(47 48 54);
+ --md-sys-color-inverse-primary: rgb(73 93 146);
+ --md-sys-color-primary-fixed: rgb(218 225 255);
+ --md-sys-color-on-primary-fixed: rgb(0 24 73);
+ --md-sys-color-primary-fixed-dim: rgb(179 197 255);
+ --md-sys-color-on-primary-fixed-variant: rgb(49 69 120);
+ --md-sys-color-secondary-fixed: rgb(221 226 249);
+ --md-sys-color-on-secondary-fixed: rgb(21 27 44);
+ --md-sys-color-secondary-fixed-dim: rgb(193 198 221);
+ --md-sys-color-on-secondary-fixed-variant: rgb(65 70 89);
+ --md-sys-color-tertiary-fixed: rgb(255 214 249);
+ --md-sys-color-on-tertiary-fixed: rgb(43 18 43);
+ --md-sys-color-tertiary-fixed-dim: rgb(225 187 220);
+ --md-sys-color-on-tertiary-fixed-variant: rgb(90 61 88);
+ --md-sys-color-surface-dim: rgb(18 19 24);
+ --md-sys-color-surface-bright: rgb(56 57 63);
+ --md-sys-color-surface-container-lowest: rgb(13 14 19);
+ --md-sys-color-surface-container-low: rgb(26 27 33);
+ --md-sys-color-surface-container: rgb(30 31 37);
+ --md-sys-color-surface-container-high: rgb(41 42 47);
+ --md-sys-color-surface-container-highest: rgb(51 52 58);
+}
+
+:host {
+ display: block;
+ margin-top: 8px;
+ margin-bottom: 16px;
+}
+
+.inputTextContainer {
+ position: relative;
+ display: inline-flex;
+ width: 100%;
+ min-height: 56px;
+}
+.inputTextContainer .input {
+ font-family: var(--font-family-content);
+ font-size: var(--body-medium-font-size);
+ font-weight: var(--body-medium-font-weight);
+ letter-spacing: var(--body-medium-letter-spacing);
+ line-height: var(--body-medium-line-height);
+ width: 100%;
+ padding: 0 16px;
+ color: var(--md-sys-color-on-surface);
+ background: transparent;
+ overflow: hidden;
+ border: none;
+ outline: none;
+ z-index: 1;
+}
+.inputTextContainer .input:-webkit-autofill, .inputTextContainer .input:-webkit-autofill:hover, .inputTextContainer .input:-webkit-autofill:focus, .inputTextContainer .input:-webkit-autofill:active {
+ -webkit-text-fill-color: var(--md-sys-color-on-surface);
+ transition: background-color 5000s ease-in-out 0s;
+}
+.inputTextContainer .input::placeholder {
+ color: var(--md-sys-color-on-surface-variant);
+ opacity: 0.6;
+}
+.inputTextContainer .input:disabled {
+ color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 38%, transparent);
+}
+.inputTextContainer .label {
+ font-family: var(--font-family-content);
+ font-size: var(--body-medium-font-size);
+ font-weight: var(--body-medium-font-weight);
+ letter-spacing: var(--body-medium-letter-spacing);
+ line-height: var(--body-medium-line-height);
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ color: var(--md-sys-color-on-surface-variant);
+ pointer-events: none;
+ transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ transform-origin: left top;
+ z-index: 2;
+}
+.inputTextContainer .outline {
+ position: absolute;
+ inset: -5px 0 0 0;
+ pointer-events: none;
+ border: 1px solid var(--md-sys-color-outline);
+ border-radius: 4px;
+ transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1), border-width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+.inputTextContainer .notch {
+ font-family: var(--font-family-content);
+ font-size: var(--label-medium-font-size);
+ font-weight: var(--label-medium-font-weight);
+ letter-spacing: var(--label-medium-letter-spacing);
+ line-height: var(--label-medium-line-height);
+ display: block;
+ width: auto;
+ max-width: 0.01px;
+ padding: 0;
+ visibility: hidden;
+ transition: max-width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+.inputTextContainer .notch .notch-text {
+ padding-inline: 4px;
+}
+.inputTextContainer.focused .label, .inputTextContainer.populated .label {
+ transform: translateY(-24px) translateX(4px) scale(0.75);
+}
+.inputTextContainer.focused .notch, .inputTextContainer.populated .notch {
+ max-width: 100%;
+}
+.inputTextContainer:hover:not(.disabled):not(.focused) .outline {
+ border-color: var(--md-sys-color-on-surface);
+}
+.inputTextContainer.focused .label {
+ color: var(--md-sys-color-primary);
+}
+.inputTextContainer.focused .outline {
+ border: 2px solid var(--md-sys-color-primary);
+}
+.inputTextContainer.disabled .outline {
+ border-color: color-mix(in srgb, var(--md-sys-color-outline) 12%, transparent);
+}
+.inputTextContainer.disabled .label {
+ color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 38%, transparent);
+}
+.inputTextContainer.error .outline {
+ border-color: var(--md-sys-color-error);
+}
+.inputTextContainer.error .label {
+ color: var(--md-sys-color-error);
+}
+.inputTextContainer.error:hover:not(.disabled) .outline {
+ border-color: var(--md-sys-color-on-error-container);
+}
+.inputTextContainer.error.focused .outline {
+ border: 2px solid var(--md-sys-color-error);
+}
+
+.error-messages {
+ font-family: var(--font-family-content);
+ font-size: var(--body-small-font-size);
+ font-weight: var(--body-small-font-weight);
+ letter-spacing: var(--body-small-letter-spacing);
+ line-height: var(--body-small-line-height);
+ color: var(--md-sys-color-error);
+ margin: 4px 16px;
+}
+
+.disabled-reasons {
+ font-family: var(--font-family-content);
+ font-size: var(--body-small-font-size);
+ font-weight: var(--body-small-font-weight);
+ letter-spacing: var(--body-small-letter-spacing);
+ line-height: var(--body-small-line-height);
+ color: var(--md-sys-color-on-surface-variant);
+ margin: 4px 16px;
+}
+
+.filename {
+ font-family: var(--font-family-content);
+ font-size: var(--body-large-font-size);
+ font-weight: var(--body-large-font-weight);
+ letter-spacing: var(--body-large-letter-spacing);
+ line-height: var(--body-large-line-height);
+ background-color: transparent;
+ border: none;
+ color: var(--md-sys-color-on-surface);
+ display: block;
+ flex: 1 1 auto;
+ margin: auto 16px;
+ max-width: 100%;
+ min-width: 40px;
+ overflow: hidden;
+ padding: 4px 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+input[type=file] {
+ display: none;
+}
+
+/*# sourceMappingURL=qd-input-file.css.map */
diff --git a/projects/qd-design/src/lib/qd-input-file/qd-input-file.css.map b/projects/qd-design/src/lib/qd-input-file/qd-input-file.css.map
new file mode 100644
index 0000000..b3b59cf
--- /dev/null
+++ b/projects/qd-design/src/lib/qd-input-file/qd-input-file.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../../styles/qd_design_system.scss","../../styles/typography/typography.token.scss","../../styles/design/look_and_feel.scss","../../styles/colors/light-theme.scss","../../styles/colors/dark-theme.scss","qd-input-file.scss","../../styles/typography/typography.module.scss"],"names":[],"mappings":"AAQQ;AACA;ACTR;EAEE;EAEA;AAEA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;;;AChDF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AFhDF;EGVE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EHnCA;;;AAGF;EACE;IIjBA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AJ1BF;EItBE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AC/CF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EC2CA;EACA;EACA;EACA;EACA;ED7CE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAIE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;ECcA;EACA;EACA;EACA;EACA;EDhBE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;ECiBA;EACA;EACA;EACA;EACA;EDnBE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKF;EACE;;AAGF;EACE;;AAKF;EACE;;AAKF;EACE;;AAEF;EACE;;AAKF;EACE;;AAEF;EACE;;AAKF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAKN;EClEE;EACA;EACA;EACA;EACA;EDgEA;EACA;;;AAGF;ECxEE;EACA;EACA;EACA;EACA;EDsEA;EACA;;;AAGF;EC9FE;EACA;EACA;EACA;EACA;ED4FA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE","file":"qd-input-file.css"}
\ No newline at end of file
diff --git a/projects/qd-design/src/lib/qd-input-file/qd-input-file.html b/projects/qd-design/src/lib/qd-input-file/qd-input-file.html
new file mode 100644
index 0000000..65cdd22
--- /dev/null
+++ b/projects/qd-design/src/lib/qd-input-file/qd-input-file.html
@@ -0,0 +1,48 @@
+
+
+
{{ value().name }}
+
+
+
+
+
+
+
+
+
+@if (invalid()) {
+
+ @for (error of errors(); track error) {
+ {{ error.message }}
+ }
+
+}
+
+@if (disabled() && disabledReasons().length > 0) {
+
+ @for (reason of disabledReasons(); track reason) {
+ {{ reason.message }}
+ }
+
+}
diff --git a/projects/qd-design/src/lib/qd-input-file/qd-input-file.scss b/projects/qd-design/src/lib/qd-input-file/qd-input-file.scss
new file mode 100644
index 0000000..295f251
--- /dev/null
+++ b/projects/qd-design/src/lib/qd-input-file/qd-input-file.scss
@@ -0,0 +1,163 @@
+@use '../../styles/qd_design_system' as qd;
+
+:host {
+ display: block;
+ margin-top: 8px;
+ margin-bottom: 16px;
+}
+
+.inputTextContainer {
+ position: relative;
+ display: inline-flex;
+ width: 100%;
+ min-height: 56px;
+
+ .input {
+ @include qd.body-medium;
+ width: 100%;
+ padding: 0 16px;
+ color: var(--md-sys-color-on-surface);
+ background: transparent;
+ overflow: hidden;
+ border: none;
+ outline: none;
+ z-index: 1;
+
+ &:-webkit-autofill,
+ &:-webkit-autofill:hover,
+ &:-webkit-autofill:focus,
+ &:-webkit-autofill:active {
+ -webkit-text-fill-color: var(--md-sys-color-on-surface);
+ transition: background-color 5000s ease-in-out 0s;
+ }
+
+ &::placeholder {
+ color: var(--md-sys-color-on-surface-variant);
+ opacity: 0.6;
+ }
+
+ &:disabled {
+ color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 38%, transparent);
+ }
+ }
+
+ .label {
+ @include qd.body-medium;
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ color: var(--md-sys-color-on-surface-variant);
+ pointer-events: none;
+ transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ transform-origin: left top;
+ z-index: 2;
+ }
+
+ .outline {
+ position: absolute;
+ inset: -5px 0 0 0;
+ pointer-events: none;
+ border: 1px solid var(--md-sys-color-outline);
+ border-radius: 4px;
+ transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1), border-width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ .notch {
+ @include qd.label-medium;
+ display: block;
+ width: auto;
+ max-width: 0.01px;
+ padding: 0;
+ visibility: hidden;
+ transition: max-width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+
+ .notch-text {
+ padding-inline: 4px;
+ }
+ }
+
+ &.focused, &.populated {
+ .label {
+ transform: translateY(-24px) translateX(4px) scale(0.75);
+ }
+
+ .notch {
+ max-width: 100%;
+ }
+ }
+
+ &:hover:not(.disabled):not(.focused) {
+ .outline {
+ border-color: var(--md-sys-color-on-surface);
+ }
+ }
+
+ &.focused {
+ .label {
+ color: var(--md-sys-color-primary);
+ }
+ .outline {
+ border: 2px solid var(--md-sys-color-primary);
+ }
+ }
+
+ &.disabled {
+ .outline {
+ border-color: color-mix(in srgb, var(--md-sys-color-outline) 12%, transparent);;
+ }
+ .label {
+ color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 38%, transparent);;
+ }
+ }
+
+ &.error {
+ .outline {
+ border-color: var(--md-sys-color-error);
+ }
+
+ .label {
+ color: var(--md-sys-color-error);
+ }
+
+ &:hover:not(.disabled) .outline {
+ border-color: var(--md-sys-color-on-error-container);
+ }
+
+ &.focused .outline {
+ border: 2px solid var(--md-sys-color-error);
+ }
+ }
+}
+
+.error-messages {
+ @include qd.body-small;
+ color: var(--md-sys-color-error);
+ margin: 4px 16px
+}
+
+.disabled-reasons {
+ @include qd.body-small;
+ color: var(--md-sys-color-on-surface-variant);
+ margin: 4px 16px
+}
+
+.filename {
+ @include qd.body-large;
+ background-color: transparent;
+ border: none;
+ color: var(--md-sys-color-on-surface);
+ display: block;
+ flex: 1 1 auto;
+ margin: auto 16px;
+ max-width: 100%;
+ min-width: 40px;
+ overflow: hidden;
+ padding: 4px 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+input[type='file'] {
+ display: none;
+}
diff --git a/projects/qd-design/src/lib/qd-input-file/qd-input-file.spec.ts b/projects/qd-design/src/lib/qd-input-file/qd-input-file.spec.ts
new file mode 100644
index 0000000..ce1a70b
--- /dev/null
+++ b/projects/qd-design/src/lib/qd-input-file/qd-input-file.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { QdInputFile } from './qd-input-file';
+
+describe('QdInputFile', () => {
+ let component: QdInputFile;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [QdInputFile]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(QdInputFile);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/projects/qd-design/src/lib/qd-input-file/qd-input-file.ts b/projects/qd-design/src/lib/qd-input-file/qd-input-file.ts
new file mode 100644
index 0000000..cdcb57b
--- /dev/null
+++ b/projects/qd-design/src/lib/qd-input-file/qd-input-file.ts
@@ -0,0 +1,48 @@
+import {ChangeDetectionStrategy, Component, computed, input, model, signal} from '@angular/core';
+import {DisabledReason, FormValueControl, ValidationError, WithOptionalField} from '@angular/forms/signals';
+
+@Component({
+ standalone: true,
+ selector: 'qd-input-file',
+ templateUrl: './qd-input-file.html',
+ styleUrl: './qd-input-file.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class QdInputFile implements FormValueControl {
+ value = model(new File([], "", undefined));
+ placeholder = input('Placeholder');
+
+ // Writable interaction state - control updates these
+ touched = model(false);
+ // Read-only state - form system manages these
+ disabled = input(false);
+ disabledReasons = input[]>([]);
+ readonly = input(false);
+ hidden = input(false);
+ invalid = input(false);
+ errors = input[]>([]);
+ isFocused = signal(false);
+
+ isPopulated = computed(() => this.value().name.length > 0);
+ currentPlaceholder = computed(() => this.isFocused() ? this.placeholder() : '');
+
+ onInput(event: Event) {
+ const inputElement: HTMLInputElement = event.target as HTMLInputElement;
+ const files: FileList | null = inputElement.files;
+
+ if (files && files.length > 0) {
+ this.value.set(files[0]);
+ } else {
+ this.value.set(new File([], "", undefined));
+ }
+ }
+
+ onFocus() {
+ this.isFocused.set(true);
+ }
+
+ onBlur() {
+ this.isFocused.set(false);
+ this.touched.set(true)
+ }
+}