// The gap has to beat the distance from a field to its own error message, // which Material renders flush underneath it — 0px. At 1.25rem the message // ended up 6px from the *next* field's label and 0px from its own field, so // "Falta el nombre" read as the caption of "HORAS POR SEMANA" rather than of // the field above it. The three lines have to group with the field they belong // to, which means the space between rows must be visibly larger than the space // inside one. .landing-form { display: flex; flex-direction: column; gap: 2rem; } .landing-form__kind { border: 0; margin: 0; padding: 0; legend { padding: 0 0 0.5rem; font-weight: 600; } } .landing-form__row { display: grid; gap: 1.25rem; @media (min-width: 640px) { grid-template-columns: repeat(2, minmax(0, 1fr)); } } .landing-form__error { margin: 0; color: var(--pi-color-warn, #d64545); } // The hours field sits inside an @if, and that is what breaks its label. // // Angular Material measures an outlined field's floating label once, on init, // and writes the result to the element as an inline // `transform: var(--mat-mdc-form-field-label-transform, translateY(-50%) …)`. // A field created inside a conditional block is measured before it has any // geometry, so the value it keeps is the unfloated one: the label ends up 5px // above the wrapper instead of 18, straddling the 2px outline and struck // through by it. // // `@pi/ui` already publishes the right geometry for every other field // (`translateY(-3rem) scale(1)`, a 48px label box), and that rule matches this // one too — it simply loses to the inline style. `!important` is what beats an // inline declaration, and it is restating the library's own values, not // inventing new ones. :host ::ng-deep .pi-field.pi-form-field__number { .mdc-notched-outline .mdc-floating-label { height: var(--pi-spacing-xxxx-large) !important; } .mdc-text-field:not(.mat-form-field-no-animations) .mdc-floating-label { transform: translateY(-3rem) scale(1) !important; } } // Two things the library gets wrong once a field is in a real state. // // 1. The label changes shape when you focus it. `@pi/ui` applies the theme's // label typography under `:not(.mdc-text-field--focused)`, so text-transform, // letter-spacing and weight all fall away the moment the field takes focus: // "MENSAJE *" becomes "Mensaje *" in a lighter face, and the label visibly // reflows under the cursor. Restated here without that guard. Colour is // deliberately left alone — Material tints the label on focus, and that part // is wanted. // // 2. The focus ring is a stadium on the textarea. Both the notched outline and // the gradient ring take their radius from `--pi-theme-field-radius`, which // the `alternative` shell sets to 999px. That is right for a 48px-tall input // and wrong for a 112px-tall textarea: the two shapes stop coinciding and // the field is drawn twice, once in the error colour and once in the // gradient, offset from each other. A fixed radius makes them agree again. :host ::ng-deep { .pi-field .mdc-text-field:not(.mdc-text-field--disabled) .mdc-floating-label { text-transform: var(--pi-theme-field-label-transform, none); letter-spacing: var(--pi-theme-field-label-letter-spacing, normal); font-weight: var(--pi-theme-field-label-weight, inherit); } // A stadium is right for a 48px input and wrong for a 112px textarea, which // is all this is now — with the focus ring gone there is no second shape to // agree with. .pi-field.pi-form-field__textarea { --pi-theme-field-radius: 1.25rem; } } // Error states, which move the form around and read wrong. // // 1. Submitting an incomplete form shoved everything below it downwards. // Material's subscript strip is 10px tall while a field is valid and 24px // once it carries a message, so every errored field grew the form by 14px at // once — the fields under the cursor jumped out from under it. Reserving the // taller height up front costs 14px of whitespace and buys a form that does // not move. // // 2. `pi-checkbox` renders its message as an inline span straight after the // label, so it ran on as part of the sentence: "Acepto que guardéis mis // datos para responderme. Hace falta tu consentimiento". It belongs on its // own line, like every other field's message. :host ::ng-deep { .pi-field .mat-mdc-form-field-subscript-wrapper { min-height: 1.5rem; } // Its own line, in space reserved for it. Left in flow it would push the // submit button 25px down at the exact moment you press it — the same jump // the reserved subscript above removes for the fields. .pi-checkbox { position: relative; padding-bottom: 1.25rem; } // Nothing in the library styles this class, so the message inherited the // body colour and read as ordinary text next to four red ones. .pi-checkbox__error { display: block; position: absolute; left: 0; bottom: 0; color: var(--pi-color-warn); // `mat-error` renders the other four at 12px; this one inherited 15px. font-size: var(--pi-font-size-x-small, 0.75rem); } } // TEMPORARY. Remove when @pi/ui ships the fix. // // `pi-number-field` declares `` unconditionally, so // Material marks the field as having an icon prefix even though nothing is // projected into it. The empty prefix is 4px wide and takes the place of the // 16px inset every other field gets: the value ended up 12px to the left of // its own label, which is what makes it look wrong. // // Only two of the library's twenty fields declare a prefix, both numeric, and // only `incremental-number-field` actually uses one — so this is an oversight // rather than a pattern. The fix belongs there and is written: // `pireon-ui`, `number-field.component.html`, guarded by a `contentChild`. // This restores the inset here until that version is published. :host ::ng-deep .pi-form-field__number { .mat-mdc-form-field-icon-prefix { display: none; } .mat-mdc-form-field-infix { padding-left: 16px; } }