UNPKG

@c8y/style

Version:

Styles for Cumulocity IoT applications

1,197 lines (1,112 loc) • 28.3 kB
@import "../../../variables/_dlt-c8y-icons-vars.less"; @import "../../icons/_c8y-glyphs.less"; @import "../../mixins/_c8y-scrollbar.less"; @import "../../mixins/_forms.less"; @import "../../mixins/_grid.less"; @import "../../mixins/_icon-base.less"; /** * Forms - Form controls, inputs, labels, and validation * * Note: Uses @form-control-*, @form-label-*, @form-legend-*, and @size-* tokens. * * Intentionally hardcoded values: * - rem-based values (1rem, 0.25rem, 1.1428571429em, 1.5em, 0.9rem): Relative sizing for fieldsets * - Percentages (20%, 50%, 100%): Layout calculations * - Border widths (1px): Standard borders * - Fine-tuning offsets (1px, 2px, 3px): Precise positioning adjustments * - Decimal positions (4.4px, 3.7px, .3px): Sub-pixel positioning for checkmarks * - Component-specific sizes (30px, 32px, 180px): Fixed component dimensions * - Typography sizes (12px, 14px, 16px, 18px): Font sizes * - Checkbox/radio mark dimensions (9px, 5px): Custom control indicators * - Shadow values (0 0 0 2px, inset 0 0 0): Box-shadow specifications * - Transition durations (0.15s, 0.25s, 0.35s, 0.4s, 0.5s): Animation timing * - Opacity values: Visual effects * - Z-index values: Layering * - Transform values (rotate, translate, scale): Positioning transforms * - Line-height unitless values (1, 1.2, 1.5): Relative line heights * - Calculation expressions: Complex computed values * - Non-standard spacing (15px): Off-token legacy spacing */ // Shared mixin for validation state styling with feedback icon .validation-state(@validation-color; @icon-content) { .form-control-validation(@form-control-color-default; @validation-color; transparent); .form-control-feedback-message { &:before { content: @icon-content; } } } // Shared mixin for form-block border line .form-block-border-line() { align-self: center; flex: 1 1 auto; border-top-width: 1px; border-top-style: solid; content: ''; opacity: 0.5; } // Normalize non-controls fieldset { margin: 0; padding: 0; min-width: 0; border: 0; } fieldset.c8y-fieldset { display: block; margin: 1rem 0; border: solid 1px @form-control-border-color-default; border-radius: @size-8; margin-inline-start: 2px; margin-inline-end: 2px; padding-block-start: 0.25rem; padding-inline-end: 1.1428571429em; padding-block-end: 0; padding-inline-start: 1.1428571429em; min-inline-size: 100%; &--lg { padding-block-start: 1rem; padding-inline-end: 1.5em; padding-block-end: 0; padding-inline-start: 1.5em; } &.expanded { margin: 1rem -1.15rem; } legend { margin: 0; width: auto; text-transform: none; padding-inline-start: @size-4; padding-inline-end: @size-4; min-height: @size-24; display: flex; align-items: center; } legend[align='left'] { justify-self: left; } legend[align='center'] { justify-self: center; } legend[align='right'] { justify-self: right; } + .c8y-fieldset { margin-top: @size-24; } } legend, .legend { display: block; margin: @size-16 0 @size-8 0; padding: 0; width: 100%; border: 0; color: @form-legend-color; text-transform: @form-legend-text-transform; font-weight: @form-legend-font-weight; font-size: @form-legend-font-size; line-height: inherit; &.form-block { display: flex; align-items: center; flex-flow: row nowrap; &:after { .form-block-border-line(); margin-left: @size-8; } &.center { &:before { .form-block-border-line(); margin-right: @size-8; } } &.last-record { margin-right: auto; margin-left: auto; max-width: 180px; > [class^='dlt-c8y-icon-'], > [class*=' dlt-c8y-icon-'] { font-size: 0.5rem; } } } > .dot { margin-right: @size-5; width: 30px; height: 30px; font-size: 14px; line-height: 32px; &--small { width: @size-20; height: @size-20; font-size: 12px; display: flex; align-items: center; justify-content: center; border-radius: 50%; } } } label { display: inline-block; margin-bottom: @size-4; max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) color: @form-label-color; text-transform: @form-label-text-transform; font-weight: @form-label-font-weight; font-size: @form-label-font-size; font-family: @form-control-font-family; > a { display: inline-block; font-size: inherit; } &[tooltip], [tooltip], [uib-tooltip] { cursor: pointer; } .form-group & { display: block; } fieldset[disabled] & { &:not(.c8y-checkbox):not(.c8y-radio) { opacity: @form-control-disabled-opacity; } } &:has(.btn-help) { display: flex; align-items: center; } } // Normalize form controls // While most of our form styles require extra classes, some basic normalization // is required to ensure optimum display with or without those classes to better address browser inconsistencies. // Override content-box in Normalize (* isn't specific enough) input[type='search'] { .box-sizing(border-box); } // Position radios and checkboxes better input[type='radio'], input[type='checkbox'] { margin: 1px 0 0; margin-top: 1px \9; // IE8-9 font-size: 16px; line-height: normal; } .plain input[type='checkbox'] { margin: 3px 0 0; } input[type='file'] { display: block; } // remove icon in chrome input[type='date']::-webkit-inner-spin-button, input[type='date']::-webkit-calendar-picker-indicator { display: none; -webkit-appearance: none; } // Make multiple select elements height not fixed select[multiple], select[size] { height: auto; } // Adjust output element output { display: block; color: @form-control-color-default; font-size: @font-size-base; line-height: inherit; } // Common form controls // Shared size and type resets for form controls. Apply `.form-control` to any of the following form controls: .form-control { display: block; padding: @form-control-padding-base-vertical @form-control-padding-base-horizontal; width: 100%; height: @form-control-height-base; border: 0; border-radius: @form-control-border-radius; background-color: @form-control-background-default; background-image: none; // Reset unusual Firefox-on-Android default style,see https://github.com/necolas/normalize.css/issues/214 box-shadow: inset 0 0 0 @form-control-border-width @form-control-border-color-default; color: @form-control-color-default; font-weight: @form-control-font-weight; font-size: @font-size-base; font-family: @form-control-font-family; line-height: @form-control-line-height; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; .form-control-focus(); .placeholder(); &:has(.c8y-colorpicker) { width: auto; } &[disabled], fieldset[disabled] & { opacity: @form-control-disabled-opacity; } &[readonly], &[readonly]:focus { background-color: @form-control-background-default; color: @text-muted; opacity: 1; } &[disabled], fieldset[disabled] & { cursor: @cursor-disabled; } textarea & { height: auto; } } textarea.form-control { min-height: @form-control-height-base; height: auto; resize: vertical; .c8y-scrollbar(); &.no-resize { resize: none; } } // color picker .c8y-colorpicker { position: relative; width: @size-20; height: @size-20; input { position: absolute; top: 0; left: 0; z-index: 10; box-sizing: border-box; width: @size-20; height: @size-20; opacity: 0; cursor: pointer; + span { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 9; border-radius: 50%; border: 1px solid @form-control-border-color-default; } &:focus + span { box-shadow: 0 0 0 2px @form-control-border-color-focus; } } &--alarm, &--event{ input + span{ display: flex; align-items: center; justify-content: center; font-size: 14px; color: var(--component-background-default, var(--c8y-root-component-background-default, #fff)); > i{ transform: translateY(.3px); } } } } // Search inputs in iOS // // This overrides the extra rounded corners on search inputs in iOS so that our // `.form-control` class can properly style them. Note that this cannot simply // be added to `.form-control` as it's not specific enough. For details, see // https://github.com/twbs/bootstrap/issues/11586. input[type='search'] { -webkit-appearance: none; appearance: none; } select, select.form-control { // Form control base styles (from .form-control in forms.less) display: block; padding: @form-control-padding-base-vertical @form-control-padding-base-horizontal; width: 100%; height: @form-control-height-base; border: 0; border-radius: @form-control-border-radius; background-color: @form-control-background-default; box-shadow: inset 0 0 0 @form-control-border-width @form-control-border-color-default; color: @form-control-color-default; font-weight: @form-control-font-weight; font-size: @font-size-base; font-family: @form-control-font-family; line-height: @form-control-line-height; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; &[multiple], &[size] { height: auto; background-image: none; } } .c8y-select-wrapper { position: relative; select { padding-right: @size-24!important; background-image: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; &::-ms-expand { display: none; } } &:after { .c8y-glyph(); position: absolute; top: 50%; right: @size-5; z-index: 2; color: @form-control-icon-color; content: @c8y-glyph-caret; font-size: 18px; transform: translate(0, -50%); pointer-events: none; } } // Form groups // Designed to help with the organization and spacing of vertical forms. For // horizontal forms, wrap form-groups in the predefined grid classes. .form-group { display: block; margin-bottom: @form-validation-bottom-margin; } td.form-group, th.form-group { margin-bottom: 0; } // Checkboxes and radios label.c8y-checkbox, label.c8y-radio { position: relative; display: flex; align-items: center; margin: 0; color: @form-control-color-default; text-transform: none; font-weight: @form-control-font-weight; font-size: inherit; line-height: @form-control-height-base; cursor: pointer; input[type='checkbox'], input[type='radio'] { position: absolute; top: 0; left: 0; z-index: 1; margin: 0; opacity: 0; } input + span { position: relative; z-index: 2; display: inline-block; } input[type='checkbox'] + span, input[type='radio'] + span { position: relative; display: inline-block; flex-shrink: 0; width: @checkbox-size; height: @checkbox-size; border-radius: @form-control-border-radius; background-color: @form-control-background-default; box-shadow: inset 0 0 0 1px @form-control-border-color-default; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; margin: 2px 0; + span { margin-left: @size-8; line-height: @form-control-line-height; } } input[type='radio'] + span { border-radius: 50%; } &:hover, input:focus { + span { background-color: @form-control-background-focus; box-shadow: inset 0 0 0 2px @form-control-border-color-focus; color: @form-control-color-focus; } } input[type='checkbox']:checked + span::after { position: absolute; top: 4.4px; left: 3.7px; display: block; width: 9px; height: 5px; border-bottom: 2px solid; border-left: 2px solid; color: @form-control-border-color-focus; content: ''; transform: rotate(-45deg); } input[type='radio']:checked + span::after { position: absolute; top: 50%; left: 50%; display: inline-block; width: @size-8; height: @size-8; border-radius: 50%; background-color: @form-control-border-color-focus; content: ''; font-size: 10px; line-height: inherit; transform: translate(-50%, -50%); pointer-events: none; } //checkbox indeterminate input[type='checkbox']:indeterminate + span::after { position: absolute; top: 4.4px; left: @size-4; display: block; width: @size-8; height: 5px; border-bottom: 2px solid; border-left: 0; color: @form-control-border-color-focus; content: ''; } //disabled input[disabled], input[disabled]:checked { cursor: @cursor-disabled; + span { background-color: @form-control-background-disabled; opacity: @form-control-disabled-opacity; cursor: @cursor-disabled; } ~ span { opacity: @form-control-disabled-opacity; } } input[readonly], input[readonly]:checked { pointer-events: none; ~ span { opacity: @form-control-disabled-opacity; } } &.disabled, [disabled] & { cursor: @cursor-disabled; span { opacity: @form-control-disabled-opacity; cursor: @cursor-disabled; &::before { opacity: 1; } } } &.checkbox-inline, &.radio-inline { display: inline-flex; padding: 0; &:not(:last-child) { margin-right: @size-16; } } &.has-error { input + span { box-shadow: inset 0 0 0 2px @form-control-validation-error; } } } .form-group { > label + .c8y-checkbox, > label + .c8y-radio { margin-top: @size-8; } } // Form control help & feedback states // set feedback min height to avoid field jumping c8y-error-feedback, c8y-messages, .c8y-messages { display: block; margin-bottom: calc(@form-validation-bottom-margin * -1); min-height: @form-validation-bottom-margin; .has-error.form-group--tooltip-validation &, .input-group-array .has-error & { display: none; .form-control-feedback-message:not(.ng-inactive) { padding-top: 0.25em; margin-top: 0; &::before { color: @tooltip-color-default; } } } .has-error.form-group--tooltip-validation & { .form-control-feedback-message:before { color: var(--c8y-palette-high) !important; } } .has-error.form-group--tooltip-validation:hover &, .input-group-array .has-error:hover & { position: absolute; bottom: 61px; left: calc(20% - @size-16); z-index: 10; display: block; padding: 0 @size-5; max-width: calc(100% - @size-16); border-radius: @tooltip-radius; background-color: @form-control-validation-error; color: @tooltip-color-default; &:after { position: absolute; bottom: -5px; left: 50%; margin-top: 0; width: 0; height: 0; border-width: @tooltip-arrow-width @tooltip-arrow-width 0; border-style: solid; border-color: transparent; border-top-color: @form-control-validation-error; content: ''; opacity: @tooltip-opacity; } } } .form-group .help-block, .form-group .form-control-feedback-message { position: relative; display: block; margin-top: 0; min-height: @form-validation-bottom-margin; font-size: @font-size-small; line-height: 1.5; &:empty { display: none; } } .help-block { font-style: italic; &.has-info, .form-control-feedback-message:has(.help-block) & { display: flex; &:before { font: normal normal normal 16px/1 '@{icon-font-family}'; font-size: 16px; color: @form-control-validation-info; content: @alert-info-icon; margin-right: 2px; } } } body .form-control-feedback-message:has(.help-block) { padding-left: 0; } // icon in feedback message .form-control-feedback-message, .input-group + .help-block, select ~ .help-block, c8y-field-input ~ .help-block, textarea ~ .help-block, input ~ .help-block, file-picker ~ .help-block, .form-control ~ .help-block { &:before { position: absolute; top: 2px; left: 1px; display: inline-block; font: normal normal normal 14px/1 '@{icon-font-family}'; font-size: 16px; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } } // Apply contextual and semantic states to individual form controls. .form-control-feedback-message:not(.ng-inactive), .input-group + .help-block, select ~ .help-block, textarea ~ .help-block, file-picker ~ .help-block, .form-control ~ .help-block, c8y-field-input ~ .help-block, input ~ .help-block { position: relative; padding-top: calc(@size-base * 0.25); padding-left: @size-20; margin-top: 0; } file-picker ~ .help-block { margin-top: calc(-1 * @size-24); } .has-success:not(.schema-form-text) { .validation-state(@form-control-validation-success; @alert-success-icon); } .has-warning { .validation-state(@form-control-validation-warning; @alert-warning-icon); } .has-error { .validation-state(@form-control-validation-error; @alert-danger-icon); } .form-control-feedback-message.has-error { margin-bottom: @size-8; line-height: 1.2; &:before { color: @form-control-validation-error; content: @alert-danger-icon; } .table-data-grid & { display: none !important; } &:has(formly-validation-message:empty) { &::before { display: none; } } } .has-info { .validation-state(@form-control-validation-info; @alert-info-icon); } textarea ~ .help-block:not(:empty), file-picker ~ .help-block:not(:empty), select ~ .help-block:not(:empty), input ~ .help-block:not(:empty), c8y-field-input ~ .help-block:not(:empty), .form-control ~ .help-block:not(:empty), .input-group ~ .help-block:not(:empty) { &:before { color: @form-control-validation-info; content: @alert-info-icon; } } // legacy compliant .form-control.ng-invalid.ng-invalid-required.ng-touched, .form-control.ng-invalid.ng-touched { box-shadow: inset 1px 0 0 0 @form-control-border-color-default, inset -1px 0 0 0 @form-control-border-color-default, inset 0 1px 0 0 @form-control-border-color-default, inset 0 -4px 0 @form-control-validation-error; &:focus { box-shadow: inset 2px 0 0 0 @form-control-border-color-focus, inset -2px 0 0 0 @form-control-border-color-focus, inset 0 2px 0 0 @form-control-border-color-focus, inset 0 -4px 0 @form-control-validation-error; } + * > .form-control-feedback-message:not(:empty) { &:before { color: @form-control-validation-error; content: @alert-danger-icon; } } } // error message for drop-zone .drop-zone .has-errors .form-control-feedback-message { font-size: @font-size-base; &:not(:empty):before { color: @form-control-validation-error; content: @alert-danger-icon; } } // Feedback icon // hidden - it was deprecated .form-control-feedback { display: none !important; } // Static form control text // // Apply class to a `p` element to make any string of text align with labels in // a horizontal form layout. .form-control-static { display: flex; align-items: center; margin: 1px 0 0; min-height: @form-control-height-base; line-height: @form-control-line-height; } // Form control sizing // Build on `.form-control` with modifier classes to decrease or increase the // height and font-size of form controls. // .form-group-sm { .btn{ &:extend(.btn-sm); } > label { margin-bottom: 0; font-size: @font-size-small; } } .form-group-sm .form-control, .form-group-sm > .form-group .form-control, .form-group-sm .form-control-static, .input-sm , .input-group-sm .form-control { &:not(.c8y-radio):not(.c8y-checkbox) { padding: @form-control-padding-small-vertical @form-control-padding-small-horizontal; height: @form-control-height-sm !important; font-size: @font-size-small; line-height: @line-height-small; } } .form-group-lg > label { font-size: @font-size-large; } .form-group-lg .form-control, .form-group-lg .form-control-static, .input-lg, .input-group-lg .form-control { &:not(.c8y-radio):not(.c8y-checkbox) { padding: @form-control-padding-large-vertical @form-control-padding-large-horizontal; max-height: unset !important; height: @form-control-height-lg !important; font-size: @font-size-large; } } // disable editing in forms .form-read-only { position: relative; // covers all inputs within the form label { pointer-events: none; } input, select{ pointer-events: none; } // Removed unused modifier: &.hidden-labels .form-group { margin: 0; label { margin: 0; color: @form-label-color !important; opacity: 1 !important; } } .form-control, .form-control.input-sm, .form-control.input-lg { padding: 0; background-color: transparent; box-shadow: none; opacity: 1 !important; resize: none; &.ng-empty { display: none; } } textarea.form-control { height: auto; line-height: @form-control-line-height; } .btn:not(.form-edit-btn) { display: none; } .form-edit-btn { display: inline-block; } input[type='number'] { -moz-appearance: textfield; -webkit-appearance: none; appearance: none; } } // hide the .form-edit-btn outside a .form-read-only container .form-edit-btn { position: relative; z-index: 1001; display: none; margin: 0; padding: 0; border: 0; background: none; color: @link-color; font-size: @font-size-small; cursor: pointer; &:hover { color: @link-color-hover; text-decoration: none; } &:focus { outline: none; box-shadow: inset 0 -2px 0 @form-control-border-color-focus; } } // Editable inputs label.editable { position: relative; display: flex; align-items: flex-start; margin: 0; padding: 0; color: @form-control-color-default; text-transform: none; font-weight: inherit; font-size: inherit; cursor: pointer; .form-control { min-inline-size: 6ch; max-inline-size: 100%; max-block-size: 100%; width: auto!important; -moz-appearance: textfield; appearance: textfield; &::-webkit-inner-spin-button, &::-webkit-outer-spin-button { margin: 0; -webkit-appearance: none; } &[c8y-textarea-autoresize] { transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, padding 0.35s ease !important; } } &:has(.ng-pristine):after { .dlt-c8y-icon(); position: relative; display: inline-block; margin-top: 0.9rem; color: @component-brand-primary; content: @dlt-c8y-icon-pencil; &:has(.input-sm) { margin-top: 0.6rem; } } &:has(.input-sm) { &:has(.ng-pristine):after { margin-top: 0.6rem; } } .form-control + span { display: none; } &:not(.updated) { .form-control:not(.ng-dirty) { position: relative; z-index: 9; transition: all 0.35s ease; &[c8y-textarea-autoresize] { transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, padding 0.35s ease !important; } &:not(:focus):not(:hover) { z-index: 2; overflow: hidden; padding-right: 0; padding-left: 0; background-color: transparent; box-shadow: none; text-overflow: ellipsis; } } } &.updated { width: 100%; &:after { display: none; } } &.updated .form-control, .form-control:focus, .form-control.ng-dirty { margin-right: calc(-1 * @size-24); min-width: 100%; opacity: 1; transition: all 0.25s ease; ~ span { display: none; } } &:not(.updated):hover { .form-control { min-width: 100%; } &:after { opacity: 0; } .form-control:not(:focus) { background-color: transparent; } } .form-control.ng-invalid-required + span, .form-control.ng-invalid-required + span span { color: @form-control-validation-error !important; &:after { color: @form-control-validation-error !important; } } .has-error &, .has-warning &, .has-info &, .has-success & { &:after { display: none; } .form-control { padding: @form-control-padding-base-vertical @form-control-padding-base-horizontal !important; } } } // Inline forms // // Make forms appear inline(-block) by adding the `.form-inline` class. Inline // forms begin stacked on extra small (mobile) devices and then go inline when // viewports reach <768px. // // Requires wrapping inputs and labels with `.form-group` for proper display of // default HTML form controls and our custom form controls (e.g., input groups). // // Heads up! This is mixin-ed into `.navbar-form` in navbars.less. .form-inline { @media (min-width: @screen-sm-min) { .form-group { display: inline-block; margin-bottom: 0; max-height: 32px !important; vertical-align: middle; > label { margin-right: @size-8; } + .form-group, + .btn { margin-left: @size-8; } } label { display: inline-block; margin-bottom: 0; } .form-control { display: inline-block; width: auto; vertical-align: middle; } .form-control-static { display: inline-block; } .input-group { display: inline-flex; width: auto; vertical-align: middle; .input-group-addon, .input-group-btn, .form-control { width: auto; } } .control-label { margin-bottom: 0; vertical-align: middle; } .c8y-select-wrapper { display: inline-block; vertical-align: middle; } .radio, .checkbox { display: inline-block; margin-top: 0; margin-bottom: 0; vertical-align: middle; label { padding-left: 0; } } .radio input[type='radio'], .checkbox input[type='checkbox'] { position: relative; margin-left: 0; } // Re-override the feedback icon. // Removed unused logic: .has-feedback } } // Legacy radio and checkboxes .radio, .checkbox { label { display: inline-flex; align-items: center; margin: 0 0 @size-8 0; padding-left: 0; > input[type='radio'], > input[type='checkbox'] { flex-grow: 0; margin: 0 @size-8 0 0; height: 18px; + span { flex-grow: 1; font-weight: normal; } } } } // Horizontal forms // Horizontal forms are built on grid classes and allow you to create forms with labels on the left and inputs on the right. .form-horizontal { .radio, .checkbox, .radio-inline, .checkbox-inline { margin-top: 0; margin-bottom: 0; padding-top: calc(@component-padding-base-vertical + 1px); } .radio, .checkbox { min-height: calc(~'@{line-height-computed} + @{component-padding-base-vertical} + 1'); } // Make form groups behave like rows .form-group { .make-row(); .form-group { position: relative; margin-right: 0; margin-left: 0; .form-control-feedback { right: @size-5; } } } @media (min-width: @screen-sm-min) { .control-label { margin-bottom: 0; padding-top: calc(@component-padding-base-vertical + 2px); } } // Validation states // // Reposition the icon because it's now within a grid column and columns have // `position: relative;` on them. Also accounts for the grid gutter padding. // Removed unused logic: .has-feedback .form-control-feedback .form-group-lg { @media (min-width: @screen-sm-min) { .control-label { padding-top: calc(~'@{component-padding-large-vertical} * @{line-height-large} + 1'); font-size: @font-size-large; } } } .form-group-sm { @media (min-width: @screen-sm-min) { .control-label { padding-top: calc(~'@{component-padding-small-vertical} + 1'); font-size: @font-size-small; } } } } // Removed unused form button wrapper class - verified 0 usages: .btn-save-wrapper