diff --git a/src/theme/app.scss b/src/theme/app.scss
index 1e88bf9ee..63c199dad 100644
--- a/src/theme/app.scss
+++ b/src/theme/app.scss
@@ -1,3 +1,31 @@
+ // Common styles.
+ .text-left { text-align: left; }
+ .text-right { text-align: right; }
+ .text-center { text-align: center; }
+ .text-justify { text-align: justify; }
+ .clearfix {
+ &:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+ }
+ .img-responsive {
+ display: block;
+ max-width: 100%;
+ &[height] {
+ height: auto;
+ }
+ }
+
+ .opacity-hide { opacity: 0; }
+ .core-big { font-size: 115%; }
+ .invisible { visibility: hidden; }
+
+ .button-no-uppercase {
+ text-transform: none;
+ }
+
// Correctly inherit ion-text-wrap onto labels.
ion-item.ion-text-wrap ion-label {
white-space: normal !important;
@@ -170,7 +198,8 @@ ion-toolbar {
--border-color: var(--ion-color-danger);
}
.item-dimmed {
- opacity: 0.71;
+ opacity: 0.7;
+ --background: var(--gray-lighter);
}
// Extra text colors.
@@ -301,15 +330,53 @@ ion-item img.core-module-icon[slot="start"] {
// Select.
ion-select.core-button-select,
.core-button-select {
- background: var(--ion-color-primary-contrast);
- color: var(--ion-color-primary);
- white-space: normal;
+ --background: var(--core-button-select-background);
+ background: var(--background);
+ --color: var(--ion-color-primary);
+ color: var(--color);
+ text-overflow: ellipsis;
+ white-space: nowrap;
min-height: 45px;
+ overflow: hidden;
margin: 8px;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12);
&::part(icon) {
margin: 0 8px;
}
+ .core-button-select-text {
+ margin-inline-end: auto;
+ }
+
+}
+ion-button.core-button-select {
+ &::part(native) {
+ text-transform: none;
+ font-weight: 400;
+ font-size: 16px;
+ }
+ ion-icon {
+ margin: 0 8px;
+ }
+ .select-icon {
+ width: 19px;
+ height: 19px;
+ position: relative;
+ opacity: 0.33;
+
+ .select-icon-inner {
+ left: 5px;
+ top: 50%;
+ margin-top: -2px;
+ position: absolute;
+ width: 0px;
+ height: 0px;
+ color: currentcolor;
+ pointer-events: none;
+ border-top: 5px solid;
+ border-right: 5px solid transparent;
+ border-left: 5px solid transparent;
+ }
+ }
}
// File uploader.
@@ -346,3 +413,5 @@ ion-select.core-button-select,
font-weight: normal;
font-size: 1em;
}
+
+@import "./format-text.scss";
diff --git a/src/theme/format-text.scss b/src/theme/format-text.scss
new file mode 100644
index 000000000..149e2e807
--- /dev/null
+++ b/src/theme/format-text.scss
@@ -0,0 +1,83 @@
+@import "./mixins.scss";
+
+/** Format Text - Show more styles. */
+/** Styles of elements inside the directive should be placed in format-text.scss */
+core-format-text {
+ user-select: text;
+ word-break: break-word;
+ word-wrap: break-word;
+
+ &[maxHeight],
+ &[ng-reflect-max-height] {
+ display: block;
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+
+ /* Force display inline */
+ &.inline {
+ display: inline-block;
+ width: auto;
+ }
+
+ // This is to allow clicks in radio/checkbox content.
+ &.core-text-formatted {
+ cursor: pointer;
+ pointer-events: auto;
+
+ .core-show-more {
+ display: none;
+ }
+
+ &:not(.core-shortened) {
+ max-height: none !important;
+ }
+
+ &.core-shortened {
+ overflow: hidden;
+ min-height: 50px;
+
+ .core-show-more {
+ text-align: end;
+ font-size: 14px;
+ display: block;
+ position: absolute;
+ @include position(null, 0, 0, null);
+ z-index: 7;
+ background-color: var(--background);
+ color: var(--color);
+ padding-left: 10px; // RTL
+ /*@include darkmode() {
+ color: var(--white);
+ background-color: $core-dark-item-bg-color;
+ }*/
+ }
+
+ &:before {
+ content: '';
+ height: 100%;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: -moz-linear-gradient(top, rgba(0, 0, 0, 0) calc(100% - 50px), var(--background) calc(100% - 15px));
+ background: -webkit-gradient(left top, left bottom, color-stop(calc(100% - 50px), rgba(0, 0, 0, 0)), color-stop(calc(100% - 15px), var(--background)));
+ background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) calc(100% - 50px), var(--background) calc(100% - 15px));
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0) calc(100% - 50px), var(--background) calc(100% - 15px));
+ z-index: 6;
+ }
+ }
+ }
+
+ &.core-expand-in-fullview {
+ .core-show-more {
+ @include push-arrow-color(dedede, true);
+ @include padding-horizontal(null, 18px);
+ @include background-position(end, 0, center);
+
+ background-repeat: no-repeat;
+ background-size: 14px 14px;
+ }
+ }
+ }
+}
diff --git a/src/theme/global.scss b/src/theme/global.scss
index 44997bd1f..54f8ccf5d 100644
--- a/src/theme/global.scss
+++ b/src/theme/global.scss
@@ -11,6 +11,7 @@
/* Application styles */
@import "./variables.scss";
+@import "./mixins.scss";
/* Core CSS required for Ionic components to work properly */
@import "~@ionic/angular/css/core.css";
diff --git a/src/theme/mixins.scss b/src/theme/mixins.scss
new file mode 100644
index 000000000..93c952668
--- /dev/null
+++ b/src/theme/mixins.scss
@@ -0,0 +1,697 @@
+// Place here our custom mixins.
+@mixin core-transition($where: all, $time: 500ms) {
+ -webkit-transition: $where $time ease-in-out;
+ -moz-transition: $where $time ease-in-out;
+ -ms-transition: $where $time ease-in-out;
+ -o-transition: $where $time ease-in-out;
+ transition: $where $time ease-in-out;
+}
+
+@mixin push-arrow-color($color: dedede, $flip-rtl: false) {
+ $svg: "
";
+ @if $flip-rtl != true {
+ @include multi-dir() {
+ background-image: url("data:image/svg+xml;charset=utf-8,#{$svg}");
+ }
+ } @else {
+ $flipped-svg: "
";
+
+ @include ltr () {
+ background-image: url("data:image/svg+xml;charset=utf-8,#{$svg}");
+ }
+ @include rtl() {
+ background-image: url("data:image/svg+xml;charset=utf-8,#{$flipped-svg}");
+ }
+ }
+}
+
+@mixin border-start($px, $type, $color) {
+ @include ltr() {
+ border-left: $px $type $color;
+ }
+
+ @include rtl() {
+ border-right: $px $type $color;
+ }
+}
+
+
+@mixin border-end($px, $type, $color) {
+ @include ltr() {
+ border-right: $px $type $color;
+ }
+
+ @include rtl() {
+ border-left: $px $type $color;
+ }
+}
+
+@mixin safe-area-border-start($px, $type, $color) {
+ $safe-area-position: calc(constant(safe-area-inset-left) + #{$px});
+ $safe-area-position-env: calc(env(safe-area-inset-left) + #{$px});
+
+ @include border-start($px, $type, $color);
+ @media screen and (orientation: landscape) {
+ @include border-start($safe-area-position, $type, $color);
+ @include border-start($safe-area-position-env, $type, $color);
+ }
+}
+
+@mixin safe-area-border-end($px, $type, $color) {
+ $safe-area-position: calc(constant(safe-area-inset-right) + #{$px});
+ $safe-area-position-env: calc(env(safe-area-inset-right) + #{$px});
+
+ @include border-end($px, $type, $color);
+ @media screen and (orientation: landscape) {
+ @include border-end($safe-area-position, $type, $color);
+ @include border-end($safe-area-position-env, $type, $color);
+ }
+}
+
+@mixin safe-area-margin-horizontal($start, $end: $start) {
+ $safe-area-end: null;
+ $safe-area-start: null;
+ $safe-area-start-env: null;
+ $safe-area-end-env: null;
+
+ @if ($end) {
+ $safe-area-end: calc(constant(safe-area-inset-right) + #{$end});
+ $safe-area-end-env: calc(env(safe-area-inset-right) + #{$end});
+ }
+ @if ($start) {
+ $safe-area-start: calc(constant(safe-area-inset-left) + #{$start});
+ $safe-area-start-env: calc(env(safe-area-inset-left) + #{$start});
+ }
+
+ @include margin-horizontal($start, $end);
+
+ @media screen and (orientation: landscape) {
+ @include margin-horizontal($safe-area-start, $safe-area-end);
+ @include margin-horizontal($safe-area-start-env, $safe-area-end-env);
+ }
+}
+
+@mixin safe-area-padding-start($start, $end) {
+ $safe-area-start: calc(constant(safe-area-inset-left) + #{$start});
+ $safe-area-start-env: calc(env(safe-area-inset-left) + #{$start});
+
+ @include padding-horizontal($start, $end);
+
+ @media screen and (orientation: landscape) {
+ @include padding-horizontal($safe-area-start, $end);
+ @include padding-horizontal($safe-area-start-env, $end);
+ }
+}
+
+@mixin safe-area-padding-end($start, $end) {
+ $safe-area-end: calc(constant(safe-area-inset-right) + #{$end});
+ $safe-area-end-env: calc(env(safe-area-inset-right) + #{$end});
+
+ @include padding-horizontal($start, $end);
+
+ @media screen and (orientation: landscape) {
+ @include padding-horizontal($start, $safe-area-end);
+ @include padding-horizontal($start, $safe-area-end-env);
+ }
+}
+
+@mixin safe-area-position($top: null, $end: null, $bottom: null, $start: null) {
+ @include position-horizontal($start, $end);
+ @include safe-position-horizontal($start, $end);
+ top: $top;
+ bottom: $bottom;
+}
+
+@mixin core-headings() {
+ h1 {
+ font-size: 3rem;
+ }
+ h2 {
+ font-size: 2.8rem;
+ }
+ h3 {
+ font-size: 2.6rem;
+ }
+ h4 {
+ font-size: 2.2rem;
+ }
+ h5 {
+ font-size: 1.8rem;
+ }
+ h6 {
+ font-size: 1.4rem;
+ }
+}
+
+@mixin core-as-items() {
+ .item-md.item-block > .item-inner {
+ border-bottom: 1px solid $list-md-border-color;
+ }
+
+ .item-ios.item-block > .item-inner {
+ border-bottom: $hairlines-width solid $list-ios-border-color;
+ }
+
+ &:last-child .item > .item-inner {
+ border-bottom: 0;
+ }
+}
+
+@mixin core-items() {
+ &.item-md.item-block > .item-inner {
+ border-bottom: 1px solid $list-md-border-color;
+ }
+
+ &.item-ios.item-block > .item-inner {
+ border-bottom: $hairlines-width solid $list-ios-border-color;
+ }
+
+ &.item-block:last-child > .item-inner {
+ border-bottom: 0;
+ }
+}
+
+@mixin darkmode() {
+ $root: #{&};
+
+ @at-root body.dark {
+ #{$root} {
+ @content;
+ }
+ }
+}
+
+// Extracted from ionic.mixins.scss
+// https://github.com/ionic-team/ionic-framework/blob/master/core/src/themes/ionic.mixins.scss
+@mixin input-cover() {
+ @include position(0, null, null, 0);
+ @include margin(0);
+
+ position: absolute;
+
+ width: 100%;
+ height: 100%;
+
+ border: 0;
+ background: transparent;
+ cursor: pointer;
+
+ appearance: none;
+ outline: none;
+
+ &::-moz-focus-inner {
+ border: 0;
+ }
+}
+
+@mixin text-inherit() {
+ font-family: inherit;
+ font-size: inherit;
+ font-style: inherit;
+ font-weight: inherit;
+ letter-spacing: inherit;
+ text-decoration: inherit;
+ text-indent: inherit;
+ text-overflow: inherit;
+ text-transform: inherit;
+ text-align: inherit;
+ white-space: inherit;
+ color: inherit;
+}
+
+@mixin button-state() {
+ @include position(0, 0, 0, 0);
+
+ position: absolute;
+
+ content: "";
+
+ opacity: 0;
+}
+
+// Font smoothing
+// --------------------------------------------------
+
+@mixin font-smoothing() {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+}
+
+// Get the key from a map based on the index
+@function index-to-key($map, $index) {
+ $keys: map-keys($map);
+
+ @return nth($keys, $index);
+}
+
+
+// Breakpoint Mixins
+// ---------------------------------------------------------------------------------
+
+// Breakpoint viewport sizes and media queries.
+//
+// Breakpoints are defined as a map of (name: minimum width), order from small to large:
+//
+// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)
+//
+// The map defined in the `$screen-breakpoints` global variable is used as the `$breakpoints` argument by default.
+
+// ---------------------------------------------------------------------------------
+
+// Minimum breakpoint width. Null for the smallest (first) breakpoint.
+//
+// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+// 576px
+@function breakpoint-min($name, $breakpoints: $screen-breakpoints) {
+ $min: map-get($breakpoints, $name);
+
+ @return if($name != index-to-key($breakpoints, 1), $min, null);
+}
+
+// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash infront.
+// Useful for making responsive utilities.
+//
+// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+// "" (Returns a blank string)
+// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+// "-sm"
+@function breakpoint-infix($name, $breakpoints: $screen-breakpoints) {
+ @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}");
+}
+
+// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
+// Makes the @content apply to the given breakpoint and wider.
+@mixin media-breakpoint-up($name, $breakpoints: $screen-breakpoints) {
+ $min: breakpoint-min($name, $breakpoints);
+ @if $min {
+ @media (min-width: $min) {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+// Name of the next breakpoint, or null for the last breakpoint.
+//
+// >> breakpoint-next(sm)
+// md
+// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+// md
+// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))
+// md
+@function breakpoint-next($name, $breakpoints: $screen-breakpoints, $breakpoint-names: map-keys($breakpoints)) {
+ $n: index($breakpoint-names, $name);
+ @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);
+}
+
+// Maximum breakpoint width. Null for the smallest (first) breakpoint.
+// The maximum value is reduced by 0.02px to work around the limitations of
+// `min-` and `max-` prefixes and viewports with fractional widths.
+//
+// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max
+// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. // Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.
+// See https://bugs.webkit.org/show_bug.cgi?id=178261 // See https://bugs.webkit.org/show_bug.cgi?id=178261
+//
+// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
+// 767.98px
+@function breakpoint-max($name, $breakpoints: $screen-breakpoints) {
+ $max: map-get($breakpoints, $name);
+ @return if($max and $max > 0, $max - .02, null);
+}
+
+// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
+// Makes the @content apply to the given breakpoint and narrower.
+@mixin media-breakpoint-down($name, $breakpoints: $screen-breakpoints) {
+ $max: breakpoint-max($name, $breakpoints);
+ @if $max {
+ @media (max-width: $max) {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+
+// Text Direction - ltr / rtl
+//
+// CSS defaults to use the ltr css, and adds [dir=rtl] selectors
+// to override ltr defaults.
+// ----------------------------------------------------------
+
+@mixin multi-dir() {
+ @content;
+
+ // $root: #{&};
+ // @at-root [dir] {
+ // #{$root} {
+ // @content;
+ // }
+ // }
+}
+
+@mixin rtl() {
+ $root: #{&};
+
+ @at-root [dir=rtl] {
+ #{$root} {
+ @content;
+ }
+ }
+}
+
+@mixin ltr() {
+ @content;
+}
+
+
+// SVG Background Image Mixin
+// @param {string} $svg
+// ----------------------------------------------------------
+@mixin svg-background-image($svg, $flip-rtl: false) {
+ $url: url-encode($svg);
+ $viewBox: str-split(str-extract($svg, "viewBox='", "'"), " ");
+
+ @if $flip-rtl != true or $viewBox == null {
+ @include multi-dir() {
+ background-image: url("data:image/svg+xml;charset=utf-8,#{$url}");
+ }
+ } @else {
+ $transform: "transform='translate(#{nth($viewBox, 3)}, 0) scale(-1, 1)'";
+ $flipped-url: $svg;
+ $flipped-url: str-replace($flipped-url, "