diff --git a/projects/qd-design-demo/src/app/app.html b/projects/qd-design-demo/src/app/app.html index 3bf0f3e..a03ed8c 100644 --- a/projects/qd-design-demo/src/app/app.html +++ b/projects/qd-design-demo/src/app/app.html @@ -78,7 +78,8 @@
- + +
diff --git a/projects/qd-design-demo/src/app/app.ts b/projects/qd-design-demo/src/app/app.ts index 873d772..3444320 100644 --- a/projects/qd-design-demo/src/app/app.ts +++ b/projects/qd-design-demo/src/app/app.ts @@ -2,6 +2,7 @@ import {Component, signal} from '@angular/core'; 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 {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'; @@ -10,18 +11,19 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-root', - imports: [QdButton, QdIconButton, QdCard, NgOptimizedImage, QdTooltipDirective, QdInputText, FormField, FormsModule, ReactiveFormsModule], + imports: [QdButton, QdIconButton, QdCard, NgOptimizedImage, QdTooltipDirective, QdInputText, QdInputPassword, 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: ''}); + loginModel = signal({checkEmail: true, email: '', password: ''}); loginForm = form(this.loginModel, (schemaPath) => { required(schemaPath.email, {message: 'Email is required'}); email(schemaPath.email, {message: 'Enter a valid email address'}); disabled(schemaPath.email, ({valueOf}) => valueOf(schemaPath.checkEmail)) + required(schemaPath.password, {message: 'Password is required'}); }); public onClick() { @@ -31,5 +33,6 @@ export class App { public confirmForm() { console.log(this.loginModel().email); console.log(this.loginModel().checkEmail); + console.log(this.loginModel().password); } } diff --git a/projects/qd-design/src/lib/qd-input-password/qd-input-password.css b/projects/qd-design/src/lib/qd-input-password/qd-input-password.css new file mode 100644 index 0000000..b6f8e8e --- /dev/null +++ b/projects/qd-design/src/lib/qd-input-password/qd-input-password.css @@ -0,0 +1,413 @@ +@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 .inputIcon { + display: flex; + flex-direction: row; + flex: 1 1 auto; + width: 100%; +} +.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 8px 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 .visibilityIcon { + cursor: pointer; + margin: auto 16px auto 0; + color: var(--md-sys-color-on-surface-variant); +} +.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; +} + +/*# sourceMappingURL=qd-input-password.css.map */ diff --git a/projects/qd-design/src/lib/qd-input-password/qd-input-password.css.map b/projects/qd-design/src/lib/qd-input-password/qd-input-password.css.map new file mode 100644 index 0000000..3f82b75 --- /dev/null +++ b/projects/qd-design/src/lib/qd-input-password/qd-input-password.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-password.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;EHDA;EACA,gBGCwB;EHIxB;EGHE;;AAGF;ECsCA;EACA;EACA;EACA;EACA;EDxCE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAIE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EHeA;EGbE;EACA;;AAGF;ECGA;EACA;EACA;EACA;EACA;EDLE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;ECMA;EACA;EACA;EACA;EACA;EDRE;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;EC7EE;EACA;EACA;EACA;EACA;ED2EA;EACA;;;AAGF;ECnFE;EACA;EACA;EACA;EACA;EDiFA;EACA","file":"qd-input-password.css"} \ No newline at end of file diff --git a/projects/qd-design/src/lib/qd-input-password/qd-input-password.html b/projects/qd-design/src/lib/qd-input-password/qd-input-password.html new file mode 100644 index 0000000..9e98ce9 --- /dev/null +++ b/projects/qd-design/src/lib/qd-input-password/qd-input-password.html @@ -0,0 +1,47 @@ +
+ +
+ + +
+ + + + +
+ +@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-password/qd-input-password.scss b/projects/qd-design/src/lib/qd-input-password/qd-input-password.scss new file mode 100644 index 0000000..6ca51c3 --- /dev/null +++ b/projects/qd-design/src/lib/qd-input-password/qd-input-password.scss @@ -0,0 +1,153 @@ +@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; + + .inputIcon { + @include qd.flex-fill(row); + width: 100%; + } + + .input { + @include qd.body-medium; + width: 100%; + padding: 0 8px 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); + } + } + + .visibilityIcon { + @include qd.clickable; + margin: auto 16px auto 0; + color: var(--md-sys-color-on-surface-variant); + } + + .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 +} diff --git a/projects/qd-design/src/lib/qd-input-password/qd-input-password.spec.ts b/projects/qd-design/src/lib/qd-input-password/qd-input-password.spec.ts new file mode 100644 index 0000000..3e9df14 --- /dev/null +++ b/projects/qd-design/src/lib/qd-input-password/qd-input-password.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QdInputPassword } from './qd-input-password'; + +describe('QdInputPassword', () => { + let component: QdInputPassword; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [QdInputPassword] + }) + .compileComponents(); + + fixture = TestBed.createComponent(QdInputPassword); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/qd-design/src/lib/qd-input-password/qd-input-password.ts b/projects/qd-design/src/lib/qd-input-password/qd-input-password.ts new file mode 100644 index 0000000..a003090 --- /dev/null +++ b/projects/qd-design/src/lib/qd-input-password/qd-input-password.ts @@ -0,0 +1,58 @@ +import { + ChangeDetectionStrategy, + Component, computed, + input, + model, + signal +} from '@angular/core'; +import {DisabledReason, FormValueControl, ValidationError, WithOptionalField} from '@angular/forms/signals'; +import {QdIcon} from '../qd-icon/qd-icon'; + +@Component({ + standalone: true, + selector: 'qd-input-password', + templateUrl: './qd-input-password.html', + styleUrl: './qd-input-password.scss', + imports: [ + QdIcon + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class QdInputPassword implements FormValueControl { + value = model(''); + placeholder = input('Password'); + isPasswordVisible = signal(false) + + // 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().length > 0); + currentPlaceholder = computed(() => this.isFocused() ? this.placeholder() : ''); + + onInput(event: Event) { + const val = (event.target as HTMLInputElement).value; + this.value.set(val); + } + + onFocus() { + this.isFocused.set(true); + } + + onBlur() { + this.isFocused.set(false); + this.touched.set(true) + } + + onTapChangeVisibility() { + this.isPasswordVisible.set(!this.isPasswordVisible()) + } + +}