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()) { + +} + +@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) + } +}