diff --git a/scripts/langindex.json b/scripts/langindex.json index a381cb81c..4f962d300 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -555,6 +555,7 @@ "addon.mod_data.authorfirstname": "data", "addon.mod_data.authorlastname": "data", "addon.mod_data.confirmdeleterecord": "data", + "addon.mod_data.datemodified": "data", "addon.mod_data.descending": "data", "addon.mod_data.disapprove": "data", "addon.mod_data.edittagsnotsupported": "local_moodlemobileapp", @@ -2548,6 +2549,7 @@ "core.storingfiles": "local_moodlemobileapp", "core.strftimedate": "langconfig", "core.strftimedatefullshort": "langconfig", + "core.strftimedatemonthabbr": "langconfig", "core.strftimedateshort": "langconfig", "core.strftimedatetime": "langconfig", "core.strftimedatetimeshort": "langconfig", diff --git a/src/addons/mod/data/classes/base-field-plugin-component.ts b/src/addons/mod/data/classes/base-field-plugin-component.ts index 6fb1d032f..b045cca0b 100644 --- a/src/addons/mod/data/classes/base-field-plugin-component.ts +++ b/src/addons/mod/data/classes/base-field-plugin-component.ts @@ -74,7 +74,7 @@ export abstract class AddonModDataFieldPluginBaseComponent implements OnInit, On } /** - * Component being changed. + * @inheritdoc */ ngOnChanges(changes: { [name: string]: SimpleChange }): void { if ((this.showMode || this.listMode) && changes.value) { diff --git a/src/addons/mod/data/components/action/addon-mod-data-action.html b/src/addons/mod/data/components/action/addon-mod-data-action.html index af8482d39..af4fb1692 100644 --- a/src/addons/mod/data/components/action/addon-mod-data-action.html +++ b/src/addons/mod/data/components/action/addon-mod-data-action.html @@ -34,8 +34,12 @@ -{{ entry.timecreated * 1000 | coreFormatDate }} -{{ entry.timemodified * 1000 | coreFormatDate }} + + {{ entry.timecreated * 1000 | coreFormatDate: 'strftimedatemonthabbr' }} + + + {{ entry.timemodified * 1000 | coreFormatDate: 'strftimedatemonthabbr' }} + diff --git a/src/addons/mod/data/components/index/addon-mod-data-index.html b/src/addons/mod/data/components/index/addon-mod-data-index.html index ebf61e0a1..d71ce8244 100644 --- a/src/addons/mod/data/components/index/addon-mod-data-index.html +++ b/src/addons/mod/data/components/index/addon-mod-data-index.html @@ -80,22 +80,7 @@ - - - - - - - - - {{ 'core.next' | translate }} - - - - + @@ -104,9 +89,28 @@ +
+ + + + + + + + + {{ 'core.next' | translate }} + + + + + + +
- diff --git a/src/addons/mod/data/data-forms.scss b/src/addons/mod/data/data-forms.scss index d49231156..146abf1fc 100644 --- a/src/addons/mod/data/data-forms.scss +++ b/src/addons/mod/data/data-forms.scss @@ -3,25 +3,32 @@ // Edit and search modal. :host { - ::ng-deep { - table { - width: 100%; - } - td { - vertical-align: top; - } - } - .addon-data-advanced-search { padding: 16px; width: 100%; } - .addon-data-contents form, + .addon-data-edit-entry form, form .addon-data-advanced-search { background-color: var(--ion-item-background); ::ng-deep { + table { + width: 100%; + tbody { + display: block; + } + + td { + vertical-align: top; + } + } + + .edit-field, + .search-field { + border-bottom: 1px solid var(--stroke); + } + .has-errors { .input-highlight, .select-highlight, diff --git a/src/addons/mod/data/data.scss b/src/addons/mod/data/data.scss index f994af4ad..3bdf9069e 100644 --- a/src/addons/mod/data/data.scss +++ b/src/addons/mod/data/data.scss @@ -16,17 +16,18 @@ $grid-column-paddings: ( --border-color: var(--stroke); } -.addon-data-edit-entry { +.addon-data-contents { overflow: visible; white-space: normal; word-break: break-word; padding: 16px; - background-color: var(--ion-item-background); - border-bottom: 1px solid var(--border-color); - ::ng-deep { - table, tbody { + + @import "theme/components/moodle.scss"; + @import "theme/components/bootstrap/bootstrap_database.scss"; + + table { display: block; } diff --git a/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html b/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html index b5ac0ad6e..6ba094c34 100644 --- a/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html +++ b/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html @@ -1,16 +1,17 @@ - + + [disabled]="searchMode && searchFields && !searchFields['f_'+field.id+'_z']" presentation="date" + [showDefaultButtons]="true" /> - + {{ 'addon.mod_data.usedate' | translate }} diff --git a/src/addons/mod/data/lang.json b/src/addons/mod/data/lang.json index 3ffdfdbce..53248678c 100644 --- a/src/addons/mod/data/lang.json +++ b/src/addons/mod/data/lang.json @@ -9,6 +9,7 @@ "authorfirstname": "First name", "authorlastname": "Last name", "confirmdeleterecord": "Are you sure you want to delete this entry?", + "datemodified": "Last edited:", "descending": "Descending", "disapprove": "Undo approval", "edittagsnotsupported": "Sorry, editing tags is not supported by the app.", diff --git a/src/addons/mod/data/services/data-helper.ts b/src/addons/mod/data/services/data-helper.ts index a5df40d37..5b75afca8 100644 --- a/src/addons/mod/data/services/data-helper.ts +++ b/src/addons/mod/data/services/data-helper.ts @@ -247,7 +247,7 @@ export class AddonModDataHelperProvider { continue; } - if (action == AddonModDataAction.MOREURL) { + if (action === AddonModDataAction.MOREURL) { // Render more url directly because it can be part of an HTML attribute. template = template.replace( replaceRegex, @@ -255,10 +255,26 @@ export class AddonModDataHelperProvider { ); continue; - } else if (action == 'approvalstatus') { + } else if (action === AddonModDataAction.APPROVALSTATUS) { template = template.replace( replaceRegex, - Translate.instant('addon.mod_data.' + (entry.approved ? 'approved' : 'notapproved')), + entry.approved + ? '' + : `${Translate.instant('addon.mod_data.notapproved')}`, + ); + + continue; + } else if (action === AddonModDataAction.APPROVALSTATUSCLASS) { + template = template.replace( + replaceRegex, + entry.approved ? 'approved' : 'notapproved', + ); + + continue; + } else if (action === AddonModDataAction.ID) { + template = template.replace( + replaceRegex, + entry.id.toString(), ); continue; @@ -460,6 +476,7 @@ export class AddonModDataHelperProvider { timeadded: true, timemodified: true, tags: true, + id: true, edit: entry.canmanageentry && !entry.deleted, // This already checks capabilities and readonly period. delete: entry.canmanageentry, @@ -467,6 +484,7 @@ export class AddonModDataHelperProvider { disapprove: database.approval && accessInfo.canapprove && entry.approved && !entry.deleted, approvalstatus: database.approval, + approvalstatusclass: database.approval, comments: database.comments, actionsmenu: entry.canmanageentry @@ -504,84 +522,165 @@ export class AddonModDataHelperProvider { /** * Returns the default template of a certain type. * - * Based on Moodle function data_generate_default_template. - * * @param type Type of template. * @param fields List of database fields. * @returns Template HTML. */ - getDefaultTemplate(type: AddonModDataTemplateType, fields: AddonModDataField[]): string { - if (type == AddonModDataTemplateType.LIST_HEADER || type == AddonModDataTemplateType.LIST_FOOTER) { - return ''; + protected getDefaultTemplate(type: AddonModDataTemplateType, fields: AddonModDataField[]): string { + switch (type) { + case AddonModDataTemplateType.LIST: + return this.getDefaultListTemplate(fields); + case AddonModDataTemplateType.SINGLE: + return this.getDefaultSingleTemplate(fields); + case AddonModDataTemplateType.SEARCH: + return this.getDefaultSearchTemplate(fields); + case AddonModDataTemplateType.ADD: + return this.getDefaultAddTemplate(fields); } + return ''; + } + + /** + * Returns the default template for the list view. + * + * @param fields List of database fields. + * @returns Template HTML. + */ + protected getDefaultListTemplate(fields: AddonModDataField[]): string { const html: string[] = []; - if (type == AddonModDataTemplateType.LIST) { - html.push('##delcheck##
'); - } + html.push(` + + ##userpicture## + +

##user##

+

##timeadded##

+

+ ${Translate.instant('addon.mod_data.datemodified')} ##timemodified## +

+
+
+ ##actionsmenu## +

##approvalstatus##

+
+
- html.push( - '
', - '', - '', - ); + `); fields.forEach((field) => { - html.push( - '', - '', - '', - '', - ); + html.push(` + + ${field.name} + [[${field.name}]] + `); }); - if (type == AddonModDataTemplateType.LIST) { - html.push( - '', - '', - '', - ); - } else if (type == AddonModDataTemplateType.SINGLE) { - html.push( - '', - '', - '', - ); - } else if (type == AddonModDataTemplateType.SEARCH) { - html.push( - '', - '', - '', - '', - '', - '', - '', - '', - ); - } - - html.push( - '', - '
', - field.name, - ': [[', - field.name, - ']]
', - '##actionsmenu## ##edit## ##more## ##delete## ##approve## ##disapprove## ##export##', - '
', - '##actionsmenu## ##edit## ##delete## ##approve## ##disapprove## ##export##', - '
Author first name: ##firstname##
Author surname: ##lastname##
', - '
', - ); - - if (type == AddonModDataTemplateType.LIST) { - html.push('
'); - } + html.push('##tags##
'); return html.join(''); } + /** + * Returns the default template for the add view. + * + * @param fields List of database fields. + * @returns Template HTML. + */ + protected getDefaultAddTemplate(fields: AddonModDataField[]): string { + const html: string[] = []; + + html.push('
'); + + fields.forEach((field) => { + html.push(` +
+

${field.name}

+ [[${field.name}]] +
`); + }); + + html.push('##otherfields## ##tags##
'); + + return html.join(''); + } + + /** + * Returns the default template for the single view. + * + * @param fields List of database fields. + * @returns Template HTML. + */ + protected getDefaultSingleTemplate(fields: AddonModDataField[]): string { + const html: string[] = []; + + html.push(`
+
+ + ##userpicture## + +

##user##

+

##timeadded##

+

+ ${Translate.instant('addon.mod_data.datemodified')} ##timemodified## +

+
+
+ ##actionsmenu## +

##approvalstatus##

+
+
`); + + fields.forEach((field) => { + html.push(` + +

${field.name}

+

[[${field.name}]]

+
`); + }); + + html.push('##otherfields## ##tags##
'); + + return html.join(''); + } + + /** + * Returns the default template for the search view. + * + * @param fields List of database fields. + * @returns Template HTML. + */ + protected getDefaultSearchTemplate(fields: AddonModDataField[]): string { + const html: string[] = []; + + html.push('
'); + + html.push(` +
+

${Translate.instant('addon.mod_data.authorfirstname')}

+ ##firstname## +
`); + + html.push(` +
+

${Translate.instant('addon.mod_data.authorlastname')}

+ ##lastname## +
`); + + fields.forEach((field) => { + html.push(` +
+

${field.name}

+ [[${field.name}]] +
`); + }); + + html.push('##tags##
'); + + return html.join(''); + + } + /** * Retrieve the entered data in the edit form. * We don't use ng-model because it doesn't detect changes done by JavaScript. diff --git a/src/addons/mod/data/services/data.ts b/src/addons/mod/data/services/data.ts index 7bffea41d..41dc057fb 100644 --- a/src/addons/mod/data/services/data.ts +++ b/src/addons/mod/data/services/data.ts @@ -61,9 +61,11 @@ export enum AddonModDataAction { TIMEMODIFIED = 'timemodified', TAGS = 'tags', APPROVALSTATUS = 'approvalstatus', + APPROVALSTATUSCLASS = 'approvalstatusclass', DELCHECK = 'delcheck', // Unused. EXPORT = 'export', // Unused. ACTIONSMENU = 'actionsmenu', + ID = 'id', } export enum AddonModDataTemplateType { diff --git a/src/addons/mod/data/tests/behat/entries.feature b/src/addons/mod/data/tests/behat/entries.feature index 857b73fc9..6b1a1a3bf 100644 --- a/src/addons/mod/data/tests/behat/entries.feature +++ b/src/addons/mod/data/tests/behat/entries.feature @@ -59,7 +59,8 @@ Feature: Users can manage entries in database activities | URL | https://moodlecloud.com/ | | Description | Moodle Cloud | And I press "Save" near "Web links" in the app - And I press "Show more" near "Moodle community site" in the app + And I press "Actions menu" near "Moodle community site" in the app + And I press "Show more" in the app Then I should find "Moodle community site" in the app And I should not find "Comments" in the app And I should be able to press "Previous" in the app @@ -83,14 +84,16 @@ Feature: Users can manage entries in database activities And I set the following fields to these values in the app: | Description | Moodle community site | And I press "Save" near "Data with comments" in the app - And I press "Show more" near "Moodle community site" in the app + And I press "Actions menu" near "Moodle community site" in the app + And I press "Show more" in the app Then I should find "Moodle community site" in the app And I should find "Comments" in the app Given the following config values are set as admin: | usecomments | 0 | And I entered the data activity "Data with comments" on course "Course 1" as "student1" in the app - When I press "Show more" near "Moodle community site" in the app + When I press "Actions menu" near "Moodle community site" in the app + And I press "Show more" in the app Then I should not find "Comments" in the app But the following events should have been logged for "student1" in the app: | name | activity | activityname | course | @@ -106,9 +109,11 @@ Feature: Users can manage entries in database activities And I press "Save" near "Web links" in the app And I entered the course "Course 1" as "student2" in the app When I press "Web links" near "General" in the app + And I press "Actions menu" in the app Then "Edit" "link" should not exist And "Delete" "link" should not exist - And I press "Show more" in the app + When I press "Show more" in the app + And "Actions menu" "link" should not exist And "Edit" "link" should not exist And "Delete" "link" should not exist @@ -121,7 +126,8 @@ Feature: Users can manage entries in database activities And I press "Save" near "Web links" in the app # Edit the entry from list view. - When I press "Edit" in the app + When I press "Actions menu" in the app + And I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | | Description | Moodle Cloud | @@ -132,11 +138,13 @@ Feature: Users can manage entries in database activities And I should find "Moodle Cloud" in the app # Delete the entry from list view. - When I press "Delete" in the app + When I press "Actions menu" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Cancel" in the app And I should find "Moodle Cloud" in the app - When I press "Delete" in the app + When I press "Actions menu" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Delete" in the app And I should not find "Moodle Cloud" in the app @@ -149,7 +157,9 @@ Feature: Users can manage entries in database activities And I press "Save" near "Web links" in the app # Edit the entry from single view. - When I press "Show more" in the app + When I press "Actions menu" in the app + And I press "Show more" in the app + And I press "Actions menu" in the app And I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | @@ -161,11 +171,13 @@ Feature: Users can manage entries in database activities And I should find "Moodle Cloud" in the app # Delete the entry from list view. - When I press "Delete" in the app + When I press "Actions menu" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Cancel" in the app And I should find "Moodle Cloud" in the app - When I press "Delete" in the app + When I press "Actions menu" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Delete" in the app And I should not find "Moodle Cloud" in the app @@ -190,7 +202,8 @@ Feature: Users can manage entries in database activities And I should find "Moodle community site" in the app # Edit the entry from list view. - When I press "Edit" near "Moodle community site" in the app + When I press "Actions menu" near "Moodle community site" in the app + And I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | | Description | Moodle Cloud | @@ -201,19 +214,23 @@ Feature: Users can manage entries in database activities And I should find "Moodle Cloud" in the app # Delete the entry from list view. - When I press "Delete" near "Moodle Cloud" in the app + When I press "Actions menu" near "Moodle Cloud" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Cancel" in the app And I should find "Moodle Cloud" in the app - When I press "Delete" near "Moodle Cloud" in the app + When I press "Actions menu" near "Moodle Cloud" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Delete" in the app And I should not find "Moodle Cloud" in the app # Edit the entry from single view. - When I press "Show more" in the app + When I press "Actions menu" in the app + And I press "Show more" in the app And I should find "https://telegram.org/" in the app And I should find "Telegram" in the app + And I press "Actions menu" in the app And I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | @@ -225,11 +242,13 @@ Feature: Users can manage entries in database activities And I should find "Moodle Cloud" in the app # Delete the entry from single view. - When I press "Delete" in the app + When I press "Actions menu" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Cancel" in the app And I should find "Moodle Cloud" in the app - When I press "Delete" in the app + When I press "Actions menu" in the app + And I press "Delete" in the app Then I should find "Are you sure you want to delete this entry?" in the app And I press "Delete" in the app And I should not find "Moodle Cloud" in the app @@ -250,5 +269,5 @@ Feature: Users can manage entries in database activities And I set the following fields to these values in the app: | Number | 0 | And I press "Save" near "Number DB" in the app - Then I should find "0" near "Number:" in the app + Then I should find "0" in the app But I should not find "Save" in the app diff --git a/src/addons/mod/data/tests/behat/sync.feature b/src/addons/mod/data/tests/behat/sync.feature index b9b905b9c..d367f96c5 100644 --- a/src/addons/mod/data/tests/behat/sync.feature +++ b/src/addons/mod/data/tests/behat/sync.feature @@ -61,7 +61,8 @@ Feature: Users can store entries in database activities when offline and sync wh And I wait until the page is ready And I close the popup in the app And I switch network connection to offline - When I press "Edit" in the app + When I press "Actions menu" in the app + And I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | | Description | Moodle Cloud | @@ -83,6 +84,7 @@ Feature: Users can store entries in database activities when offline and sync wh And I press "Refresh" in the app And I wait until the page is ready And I switch network connection to offline + And I press "Actions menu" in the app And I press "Delete" in the app And I should find "Are you sure you want to delete this entry?" in the app And I press "Delete" in the app @@ -111,12 +113,14 @@ Feature: Users can store entries in database activities when offline and sync wh And I wait until the page is ready And I close the popup in the app When I switch network connection to offline + And I press "Actions menu" in the app And I press "Delete" in the app And I should find "Are you sure you want to delete this entry?" in the app And I press "Delete" in the app And I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app And I should find "This Database has offline data to be synchronised" in the app + And I press "Actions menu" in the app And I press "Restore" in the app And I press the back button in the app And I switch network connection to wifi diff --git a/src/core/features/comments/tests/behat/basic_usage.feature b/src/core/features/comments/tests/behat/basic_usage.feature index a17afe8cf..c376ddf19 100644 --- a/src/core/features/comments/tests/behat/basic_usage.feature +++ b/src/core/features/comments/tests/behat/basic_usage.feature @@ -33,6 +33,7 @@ Feature: Test basic usage of comments in app And I press "Add entries" in the app And I set the field "Test field name" to "Test" in the app And I press "Save" in the app + And I press "Actions menu" in the app And I press "Show more" in the app And I press "Comments (0)" in the app And I set the field "Add a comment..." to "comment test teacher" in the app @@ -45,6 +46,7 @@ Feature: Test basic usage of comments in app # Create and delete comments as a student Given I entered the data activity "Data" on course "Course 1" as "student1" in the app + And I press "Actions menu" in the app And I press "Show more" in the app And I press "Comments (1)" in the app And I set the field "Add a comment..." to "comment test student" in the app @@ -70,6 +72,7 @@ Feature: Test basic usage of comments in app And I press "Add entries" in the app And I set the field "Test field name" to "Test" in the app And I press "Save" in the app + And I press "Actions menu" in the app And I press "Show more" in the app And I press "Comments (0)" in the app And I switch network connection to offline diff --git a/src/core/lang.json b/src/core/lang.json index 99e49b466..0ba0d5224 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -312,6 +312,7 @@ "startingtime": "Starting time: {{$a}}", "storingfiles": "Storing files", "strftimedate": "%d %B %Y", + "strftimedatemonthabbr": "%d %b %Y", "strftimedatefullshort": "%d/%m/%y", "strftimedateshort": "%d %B", "strftimedatetime": "%d %B %Y, %I:%M %p", diff --git a/src/theme/components/atto.scss b/src/theme/components/atto.scss new file mode 100644 index 000000000..f52e4cbbe --- /dev/null +++ b/src/theme/components/atto.scss @@ -0,0 +1,73 @@ + // Atto styles +// ------------------------- +.atto_image_preview { + width: 100%; + height: 100%; + margin-left: auto; + margin-right: auto; +} + +.atto_image_preview_box { + max-height: 200px; + margin-bottom: 1em; + overflow: auto; +} + +.editor_atto_content img { + cursor: pointer; +} + +.atto_image_size { + display: inline-block; +} + +.atto_image_size input[type=checkbox] { + @include margin(null, 1em, null, 1em); +} + +.atto_image_size input[type=text] { + width: 3em; +} + +.atto_image_size label { + display: inline-block; +} + +.atto_image_button_text-top, +.atto_image_button_middle, +.atto_image_button_text-bottom, +.atto_image_button_left, +.atto_image_button_right { + vertical-align: middle; + max-width: 100%; + display: inline-block; + margin: 0 0.5em; + + &.img-responsive { + /* If the image is display: block then linking the image to URLs won't work. */ + /*display: inline-block;*/ + max-width: 100%; + } +} + +.atto_image_button_text-top { + vertical-align: text-top; +} + +.atto_image_button_middle { + vertical-align: middle; +} + +.atto_image_button_text-bottom { + vertical-align: text-bottom; +} + +.atto_image_button_left { + @include float(start); + @include margin(0, 0.5em, 0, 0); +} + +.atto_image_button_right { + @include float(end); + @include margin(0, 0, 0, 0.5em); +} diff --git a/src/theme/components/bootstrap/_alert.scss b/src/theme/components/bootstrap/_alert.scss new file mode 100644 index 000000000..437642366 --- /dev/null +++ b/src/theme/components/bootstrap/_alert.scss @@ -0,0 +1,34 @@ +.alert { + position: relative; + padding: .75rem 1.25rem; + margin-bottom: 1rem; + border: 0 solid transparent; + border-radius: .5rem; +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: 400; +} + +@each $color-name, $unused in global.$colors { + .alert-#{$color-name} { + --color-base: var(--#{$color-name}); + color: var(--#{$color-name}-shade); + border-color: var(--color-base); + background-color: var(--#{$color-name}-tint); + + .alert-link, a { + color: var(--#{$color-name}-shade); + } + } + .alert-#{$color-name} p { + color: var(--color-base); + } +} diff --git a/src/theme/components/bootstrap/_badge.scss b/src/theme/components/bootstrap/_badge.scss new file mode 100644 index 000000000..42c5d08d7 --- /dev/null +++ b/src/theme/components/bootstrap/_badge.scss @@ -0,0 +1,54 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + display: inline-block; + padding: $badge-padding-y $badge-padding-x; + @include font-size($badge-font-size); + font-weight: $badge-font-weight; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius($badge-border-radius); + @include transition($badge-transition); + + @at-root a#{&} { + @include hover-focus() { + text-decoration: none; + } + } + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} + +// Pill badges +// +// Make them extra rounded with a modifier to replace v3's badges. + +.badge-pill { + padding-right: $badge-pill-padding-x; + padding-left: $badge-pill-padding-x; + @include border-radius($badge-pill-border-radius); +} + +// Colors +// +// Contextual variations (linked badges get darker on :hover). + +@each $color, $value in $theme-colors { + .badge-#{$color} { + @include badge-variant($value); + } +} diff --git a/src/theme/components/bootstrap/_buttons.scss b/src/theme/components/bootstrap/_buttons.scss new file mode 100644 index 000000000..550229259 --- /dev/null +++ b/src/theme/components/bootstrap/_buttons.scss @@ -0,0 +1,23 @@ + +.btn-link { + background: none; +} + +button, .btn { + margin: 4px 8px; + padding-left: 12px; + padding-right: 12px; + border-radius: var(--core-input-radius); + a { + color: inherit; + } +} + +@each $color-name, $unused in global.$colors { + .btn.btn-#{$color-name} { + --color-base: var(--#{$color-name}); + color: var(--#{$color-name}-shade); + border-color: var(--color-base); + background-color: var(--#{$color-name}-tint); + } +} diff --git a/src/theme/components/bootstrap/_card.scss b/src/theme/components/bootstrap/_card.scss new file mode 100644 index 000000000..dd2112f0c --- /dev/null +++ b/src/theme/components/bootstrap/_card.scss @@ -0,0 +1,286 @@ +// +// Base styles +// + +.card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 + height: $card-height; + word-wrap: break-word; + background-color: $card-bg; + background-clip: border-box; + border: $card-border-width solid $card-border-color; + @include border-radius($card-border-radius); + + > hr { + margin-right: 0; + margin-left: 0; + } + + > .list-group { + border-top: inherit; + border-bottom: inherit; + + &:first-child { + border-top-width: 0; + @include border-top-radius($card-inner-border-radius); + } + + &:last-child { + border-bottom-width: 0; + @include border-bottom-radius($card-inner-border-radius); + } + } + + // Due to specificity of the above selector (`.card > .list-group`), we must + // use a child selector here to prevent double borders. + > .card-header + .list-group, + > .list-group + .card-footer { + border-top: 0; + } + } + + .card-body { + // Enable `flex-grow: 1` for decks and groups so that card blocks take up + // as much space as possible, ensuring footers are aligned to the bottom. + flex: 1 1 auto; + // Workaround for the image size bug in IE + // See: https://github.com/twbs/bootstrap/pull/28855 + min-height: 1px; + padding: $card-spacer-x; + color: $card-color; + } + + .card-title { + margin-bottom: $card-spacer-y; + } + + .card-subtitle { + margin-top: -$card-spacer-y * .5; + margin-bottom: 0; + } + + .card-text:last-child { + margin-bottom: 0; + } + + .card-link { + @include hover() { + text-decoration: none; + } + + + .card-link { + margin-left: $card-spacer-x; + } + } + + // + // Optional textual caps + // + + .card-header { + padding: $card-spacer-y $card-spacer-x; + margin-bottom: 0; // Removes the default margin-bottom of + color: $card-cap-color; + background-color: $card-cap-bg; + border-bottom: $card-border-width solid $card-border-color; + + &:first-child { + @include border-radius($card-inner-border-radius $card-inner-border-radius 0 0); + } + } + + .card-footer { + padding: $card-spacer-y $card-spacer-x; + color: $card-cap-color; + background-color: $card-cap-bg; + border-top: $card-border-width solid $card-border-color; + + &:last-child { + @include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius); + } + } + + + // + // Header navs + // + + .card-header-tabs { + margin-right: -$card-spacer-x * .5; + margin-bottom: -$card-spacer-y; + margin-left: -$card-spacer-x * .5; + border-bottom: 0; + } + + .card-header-pills { + margin-right: -$card-spacer-x * .5; + margin-left: -$card-spacer-x * .5; + } + + // Card image + .card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: $card-img-overlay-padding; + @include border-radius($card-inner-border-radius); + } + + .card-img, + .card-img-top, + .card-img-bottom { + flex-shrink: 0; // For IE: https://github.com/twbs/bootstrap/issues/29396 + width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch + } + + .card-img, + .card-img-top { + @include border-top-radius($card-inner-border-radius); + } + + .card-img, + .card-img-bottom { + @include border-bottom-radius($card-inner-border-radius); + } + + + // Card deck + + .card-deck { + .card { + margin-bottom: $card-deck-margin; + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + margin-right: -$card-deck-margin; + margin-left: -$card-deck-margin; + + .card { + // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + flex: 1 0 0%; + margin-right: $card-deck-margin; + margin-bottom: 0; // Override the default + margin-left: $card-deck-margin; + } + } + } + + + // + // Card groups + // + + .card-group { + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + margin-bottom: $card-group-margin; + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + flex: 1 0 0%; + margin-bottom: 0; + + + .card { + margin-left: 0; + border-left: 0; + } + + // Handle rounded corners + @if $enable-rounded { + &:not(:last-child) { + @include border-right-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-right-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-right-radius: 0; + } + } + + &:not(:first-child) { + @include border-left-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-left-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-left-radius: 0; + } + } + } + } + } + } + + + // + // Columns + // + + .card-columns { + .card { + margin-bottom: $card-columns-margin; + } + + @include media-breakpoint-up(sm) { + column-count: $card-columns-count; + column-gap: $card-columns-gap; + orphans: 1; + widows: 1; + + .card { + display: inline-block; // Don't let them vertically span multiple columns + width: 100%; // Don't let their width change + } + } + } + + + // + // Accordion + // + + .accordion { + overflow-anchor: none; + + > .card { + overflow: hidden; + + &:not(:last-of-type) { + border-bottom: 0; + @include border-bottom-radius(0); + } + + &:not(:first-of-type) { + @include border-top-radius(0); + } + + > .card-header { + @include border-radius(0); + margin-bottom: -$card-border-width; + } + } + } diff --git a/src/theme/components/bootstrap/_forms.scss b/src/theme/components/bootstrap/_forms.scss new file mode 100644 index 000000000..6d5cf10bc --- /dev/null +++ b/src/theme/components/bootstrap/_forms.scss @@ -0,0 +1,36 @@ +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; +} + +.form-check-input { + position: absolute; + margin-top: .3rem; + margin-left: -1.25rem; + + &[disabled] ~ .form-check-label, + &:disabled ~ .form-check-label { + color: global.$gray-600; + } + +} + +.form-check-label { + margin-bottom: 0; +} + +.form-check-inline { + display: inline-flex; + align-items: center; + padding-left: 0; + margin-right: .75rem; + + .form-check-input { + position: static; + margin-top: 0; + margin-right: .3125rem; + margin-left: 0; + } + +} diff --git a/src/theme/components/bootstrap/_functions.scss b/src/theme/components/bootstrap/_functions.scss new file mode 100644 index 000000000..13d9de0c1 --- /dev/null +++ b/src/theme/components/bootstrap/_functions.scss @@ -0,0 +1,190 @@ +// Bootstrap functions +// +// Utility mixins and functions for evaluating source code across our variables, maps, and mixins. + +// Ascending +// Used to evaluate Sass maps like our grid breakpoints. +@mixin _assert-ascending($map, $map-name) { + $prev-key: null; + $prev-num: null; + @each $key, $num in $map { + @if $prev-num == null or unit($num) == "%" or unit($prev-num) == "%" { + // Do nothing + } @else if not comparable($prev-num, $num) { + @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !"; + } @else if $prev-num >= $num { + @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !"; + } + $prev-key: $key; + $prev-num: $num; + } +} + +// Starts at zero +// Used to ensure the min-width of the lowest breakpoint starts at 0. +@mixin _assert-starts-at-zero($map, $map-name: "$grid-breakpoints") { + @if length($map) > 0 { + $values: map-values($map); + $first-value: nth($values, 1); + @if $first-value != 0 { + @warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}."; + } + } +} + +// Replace `$search` with `$replace` in `$string` +// Used on our SVG icon backgrounds for custom forms. +// +// @author Hugo Giraudel +// @param {String} $string - Initial string +// @param {String} $search - Substring to replace +// @param {String} $replace ('') - New value +// @return {String} - Updated string +@function str-replace($string, $search, $replace: "") { + $index: str-index($string, $search); + + @if $index { + @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); + } + + @return $string; +} + +// See https://codepen.io/kevinweber/pen/dXWoRw +// +// Requires the use of quotes around data URIs. + +@function escape-svg($string) { + @if str-index($string, "data:image/svg+xml") { + @each $char, $encoded in $escaped-characters { + // Do not escape the url brackets + @if str-index($string, "url(") == 1 { + $string: url("#{str-replace(str-slice($string, 6, -3), $char, $encoded)}"); + } @else { + $string: str-replace($string, $char, $encoded); + } + } + } + + @return $string; +} + +// Color contrast +@function color-yiq($color, $dark: $yiq-text-dark, $light: $yiq-text-light) { + $r: red($color); + $g: green($color); + $b: blue($color); + + $yiq: (($r * 299) + ($g * 587) + ($b * 114)) * .001; + + @if ($yiq >= $yiq-contrasted-threshold) { + @return $dark; + } @else { + @return $light; + } +} + +// Retrieve color Sass maps +@function color($key: "blue") { + @return map-get($colors, $key); +} + +@function theme-color($key: "primary") { + @return map-get($theme-colors, $key); +} + +@function gray($key: "100") { + @return map-get($grays, $key); +} + +// Request a theme color level +@function theme-color-level($color-name: "primary", $level: 0) { + $color: theme-color($color-name); + $color-base: if($level > 0, $black, $white); + $level: abs($level); + + @return mix($color-base, $color, $level * $theme-color-interval); +} + +// Return valid calc +@function add($value1, $value2, $return-calc: true) { + @if $value1 == null { + @return $value2; + } + + @if $value2 == null { + @return $value1; + } + + @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) { + @return $value1 + $value2; + } + + @return if($return-calc == true, calc(#{$value1} + #{$value2}), $value1 + unquote(" + ") + $value2); +} + +@function subtract($value1, $value2, $return-calc: true) { + @if $value1 == null and $value2 == null { + @return null; + } + + @if $value1 == null { + @return -$value2; + } + + @if $value2 == null { + @return $value1; + } + + @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) { + @return $value1 - $value2; + } + + @if type-of($value2) != number { + $value2: unquote("(") + $value2 + unquote(")"); + } + + @return if($return-calc == true, calc(#{$value1} - #{$value2}), $value1 + unquote(" - ") + $value2); +} + +@function divide($dividend, $divisor, $precision: 10) { + $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1); + $dividend: abs($dividend); + $divisor: abs($divisor); + @if $dividend == 0 { + @return 0; + } + @if $divisor == 0 { + @error "Cannot divide by 0"; + } + $remainder: $dividend; + $result: 0; + $factor: 10; + @while ($remainder > 0 and $precision >= 0) { + $quotient: 0; + @while ($remainder >= $divisor) { + $remainder: $remainder - $divisor; + $quotient: $quotient + 1; + } + $result: $result * 10 + $quotient; + $factor: $factor * .1; + $remainder: $remainder * 10; + $precision: $precision - 1; + @if ($precision < 0 and $remainder >= $divisor * 5) { + $result: $result + 1; + } + } + $result: $result * $factor * $sign; + $dividend-unit: unit($dividend); + $divisor-unit: unit($divisor); + $unit-map: ( + "px": 1px, + "rem": 1rem, + "em": 1em, + "%": 1% + ); + @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) { + $result: $result * map-get($unit-map, $dividend-unit); + } + @return $result; +} diff --git a/src/theme/components/bootstrap/_grid.scss b/src/theme/components/bootstrap/_grid.scss new file mode 100644 index 000000000..0bfe5303b --- /dev/null +++ b/src/theme/components/bootstrap/_grid.scss @@ -0,0 +1,73 @@ +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +@if $enable-grid-classes { + // Single container class with breakpoint max-widths + .container, + // 100% wide container at all breakpoints + .container-fluid { + @include make-container(); + } + + // Responsive containers that are 100% wide until a breakpoint + @each $breakpoint, $container-max-width in $container-max-widths { + .container-#{$breakpoint} { + @extend .container-fluid; + } + + @include media-breakpoint-up($breakpoint, $grid-breakpoints) { + %responsive-container-#{$breakpoint} { + max-width: $container-max-width; + } + + // Extend each breakpoint which is smaller or equal to the current breakpoint + $extend-breakpoint: true; + + @each $name, $width in $grid-breakpoints { + @if ($extend-breakpoint) { + .container#{breakpoint-infix($name, $grid-breakpoints)} { + @extend %responsive-container-#{$breakpoint}; + } + + // Once the current breakpoint is reached, stop extending + @if ($breakpoint == $name) { + $extend-breakpoint: false; + } + } + } + } + } +} + + +// Row +// +// Rows contain your columns. + +@if $enable-grid-classes { + .row { + @include make-row(); + } + + // Remove the negative margin from default .row, then the horizontal padding + // from all immediate children columns (to prevent runaway style inheritance). + .no-gutters { + margin-right: 0; + margin-left: 0; + + > .col, + > [class*="col-"] { + padding-right: 0; + padding-left: 0; + } + } +} + +// Columns +// +// Common styles for small and large grid columns + +@if $enable-grid-classes { + @include make-grid-columns(); +} diff --git a/src/theme/components/bootstrap/_media.scss b/src/theme/components/bootstrap/_media.scss new file mode 100644 index 000000000..b573052c1 --- /dev/null +++ b/src/theme/components/bootstrap/_media.scss @@ -0,0 +1,8 @@ +.media { + display: flex; + align-items: flex-start; +} + +.media-body { + flex: 1; +} diff --git a/src/theme/components/bootstrap/_mixins.scss b/src/theme/components/bootstrap/_mixins.scss new file mode 100644 index 000000000..7e7a23d25 --- /dev/null +++ b/src/theme/components/bootstrap/_mixins.scss @@ -0,0 +1,47 @@ +// Toggles +// +// Used in conjunction with global variables to enable certain theme features. + +// Vendor +@import "vendor/rfs"; + +// Deprecate +@import "mixins/deprecate"; + +// Utilities +@import "mixins/breakpoints"; +@import "mixins/hover"; +@import "mixins/image"; +@import "mixins/badge"; +@import "mixins/resize"; +@import "mixins/screen-reader"; +@import "mixins/size"; +@import "mixins/reset-text"; +@import "mixins/text-emphasis"; +@import "mixins/text-hide"; +@import "mixins/text-truncate"; +@import "mixins/visibility"; + +// Components +@import "mixins/alert"; +@import "mixins/buttons"; +@import "mixins/caret"; +@import "mixins/pagination"; +@import "mixins/lists"; +@import "mixins/list-group"; +@import "mixins/nav-divider"; +@import "mixins/forms"; +@import "mixins/table-row"; + +// Skins +@import "mixins/background-variant"; +@import "mixins/border-radius"; +@import "mixins/box-shadow"; +@import "mixins/gradients"; +@import "mixins/transition"; + +// Layout +@import "mixins/clearfix"; +@import "mixins/grid-framework"; +@import "mixins/grid"; +@import "mixins/float"; diff --git a/src/theme/components/bootstrap/_reboot.scss b/src/theme/components/bootstrap/_reboot.scss new file mode 100644 index 000000000..8fed4f348 --- /dev/null +++ b/src/theme/components/bootstrap/_reboot.scss @@ -0,0 +1,6 @@ +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} diff --git a/src/theme/components/bootstrap/_utilities.scss b/src/theme/components/bootstrap/_utilities.scss new file mode 100644 index 000000000..cd93459f1 --- /dev/null +++ b/src/theme/components/bootstrap/_utilities.scss @@ -0,0 +1,5 @@ +@import "utilities/align"; +@import "utilities/borders"; +@import "utilities/flex"; +@import "utilities/text"; +@import "utilities/visibility"; diff --git a/src/theme/components/bootstrap/_variables.scss b/src/theme/components/bootstrap/_variables.scss new file mode 100644 index 000000000..3459a0bcf --- /dev/null +++ b/src/theme/components/bootstrap/_variables.scss @@ -0,0 +1,217 @@ +// Variables +// +// Variables should follow the `$component-state-property-size` formula for +// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs. + +// Color system + +$white: global.$white; +$gray-100: global.$gray-100; +$gray-200: global.$gray-200; +$gray-300: global.$gray-300; +$gray-400: global.$gray-400; +$gray-500: global.$gray-500; +$gray-600: global.$gray-600; +$gray-700: global.$gray-700; +$gray-800: global.$gray-800; +$gray-900: global.$gray-900; +$black: global.$black; + +$grays: ( + "100": $gray-100, + "200": $gray-200, + "300": $gray-300, + "400": $gray-400, + "500": $gray-500, + "600": $gray-600, + "700": $gray-700, + "800": $gray-800, + "900": $gray-900 +); + +$primary: global.$primary; +$secondary: global.$secondary; +$success: global.$success; +$info: global.$info; +$warning: global.$warning; +$danger: global.$danger; +$light: global.$light; +$dark: global.$dark; + +$theme-colors: ( + "primary": $primary, + "secondary": $secondary, + "success": $success, + "info": $info, + "warning": $warning, + "danger": $danger, + "light": $light, + "dark": $dark +); + +// Set a specific jump point for requesting color jumps +$theme-color-interval: 8%; + +// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255. +$yiq-contrasted-threshold: 150; + +// Customize the light and dark text colors for use in our YIQ color contrast function. +$yiq-text-dark: $gray-900; +$yiq-text-light: $white; + +// Characters which are escaped by the escape-svg function +$escaped-characters: ( + ("<", "%3c"), + (">", "%3e"), + ("#", "%23"), + ("(", "%28"), + (")", "%29"), +); + +// Options +// +// Quickly modify global styling by enabling or disabling optional features. + +$enable-caret: true; +$enable-rounded: true; +$enable-shadows: false; +$enable-gradients: false; +$enable-transitions: true; +$enable-prefers-reduced-motion-media-query: true; +$enable-hover-media-query: false; // Deprecated, no longer affects any compiled CSS +$enable-grid-classes: true; +$enable-pointer-cursor-for-buttons: true; +$enable-print-styles: true; +$enable-responsive-font-sizes: false; +$enable-validation-icons: true; +$enable-deprecation-messages: true; + + +// Spacing +// +// Control the default styling of most Bootstrap elements by modifying these +// variables. Mostly focused on spacing. +// You can add more entries to the $spacers map, should you need more variation. + +$spacer: 1rem; +$spacers: ( + 0: 0, + 1: ($spacer * .25), + 2: ($spacer * .5), + 3: $spacer, + 4: ($spacer * 1.5), + 5: ($spacer * 3) +); + +// This variable affects the `.h-*` and `.w-*` classes. +$sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + auto: auto +); + +// Grid breakpoints +// +// Define the minimum dimensions at which your layout will change, +// adapting to different screen sizes, for use in media queries. + +$grid-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px +); + +@include _assert-ascending($grid-breakpoints, "$grid-breakpoints"); +@include _assert-starts-at-zero($grid-breakpoints, "$grid-breakpoints"); + + +// Grid containers +// +// Define the maximum width of `.container` for different screen sizes. + +$container-max-widths: ( + sm: 540px, + md: 720px, + lg: 960px, + xl: 1140px +); + +@include _assert-ascending($container-max-widths, "$container-max-widths"); + +// Grid columns +// +// Set the number of columns and specify the width of the gutters. + +$grid-columns: 12; +$grid-gutter-width: 30px; +$grid-row-columns: 6; + + +// Typography +// +// Font, line-height, and color for body text, headings, and more. + +$font-weight-lighter: lighter !default; +$font-weight-light: 300 !default; +$font-weight-normal: 400 !default; +$font-weight-bold: 700 !default; +$font-weight-bolder: bolder !default; + + +// Cards + +$card-spacer-y: .75rem; +$card-spacer-x: 1.25rem; +$card-border-width: var(--ion-card-border-width); +$card-border-radius: var(--ion-card-radius); +$card-border-color: var(--ion-card-border-color); +$card-inner-border-radius: subtract($card-border-radius, $card-border-width); +$card-cap-bg: rgba($black, .03); +$card-cap-color: null; +$card-height: null; +$card-color: null; +$card-bg: $white; + +$card-img-overlay-padding: 1.25rem; + +$card-group-margin: $grid-gutter-width * .5; +$card-deck-margin: $card-group-margin; + +$card-columns-count: 3; +$card-columns-gap: 1.25rem; +$card-columns-margin: $card-spacer-y; + + +// Buttons + Forms +// +// Shared variables that are reassigned to `$input-` and `$btn-` specific variables. + +$input-btn-focus-width: .2rem !default; + +// Buttons +// +// For each of Bootstrap's buttons, define text, background, and border color. + +$btn-focus-width: $input-btn-focus-width !default; + +$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; + +// Badges + +$badge-font-size: 75%; +$badge-font-weight: $font-weight-bold; +$badge-padding-y: .25em; +$badge-padding-x: .4em; +$badge-border-radius: var(--mdl-shape-borderRadius-lg); + +$badge-transition: $btn-transition; +$badge-focus-width: $input-btn-focus-width; + +$badge-pill-padding-x: .6em; +// Use a higher than normal value to ensure completely rounded edges when +// customizing padding or font-size on labels. +$badge-pill-border-radius: 10rem; diff --git a/src/theme/components/bootstrap/bootstrap.scss b/src/theme/components/bootstrap/bootstrap.scss new file mode 100644 index 000000000..076a9867e --- /dev/null +++ b/src/theme/components/bootstrap/bootstrap.scss @@ -0,0 +1,39 @@ +@use "theme/globals" as global; + +// Bootstrap 4 Styles +// ------------------------- + +@import "reboot"; +@import "forms"; + +@import "buttons"; +@import "alert"; +@import "media"; +@import "utilities"; + +// Deprecated styles. +.label { + display: inline-block; + padding: .25em .4em; + font-size: 75%; + font-weight: 700; + line-height: 1.1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; + color: var(--white); + background-color: var(--medium); +} + +.label-important { + color: var(--danger-contrast); + background-color: var(--danger); +} + +@each $color-name, $unused in global.$colors { + .label-#{$color-name} { + color: var(--#{$color-name}-contrast); + background-color: var(--#{$color-name}); + } +} diff --git a/src/theme/components/bootstrap/bootstrap_database.scss b/src/theme/components/bootstrap/bootstrap_database.scss new file mode 100644 index 000000000..29d2fd03c --- /dev/null +++ b/src/theme/components/bootstrap/bootstrap_database.scss @@ -0,0 +1,17 @@ +@use "theme/globals" as global; + +// Bootstrap 4 only to be applied on database (to be removed on 4.5 and included in the main bootstrap file). +// ------------------------- + +// @TODO on 4.5 Move the following lines to bootstrap file. +@import "functions"; +@import "variables"; +@import "mixins"; +@import "grid"; +@import "card"; +@import "badge"; + + // @TODO on 4.5 Move the to utilities file. +@import "utilities/spacing"; + +@import "bootstrap"; diff --git a/src/theme/components/bootstrap/mixins/_alert.scss b/src/theme/components/bootstrap/mixins/_alert.scss new file mode 100644 index 000000000..db5a7eb45 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_alert.scss @@ -0,0 +1,13 @@ +@mixin alert-variant($background, $border, $color) { + color: $color; + @include gradient-bg($background); + border-color: $border; + + hr { + border-top-color: darken($border, 5%); + } + + .alert-link { + color: darken($color, 10%); + } +} diff --git a/src/theme/components/bootstrap/mixins/_background-variant.scss b/src/theme/components/bootstrap/mixins/_background-variant.scss new file mode 100644 index 000000000..80580189a --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_background-variant.scss @@ -0,0 +1,23 @@ +// stylelint-disable declaration-no-important + +// Contextual backgrounds + +@mixin bg-variant($parent, $color, $ignore-warning: false) { + #{$parent} { + background-color: $color !important; + } + a#{$parent}, + button#{$parent} { + @include hover-focus() { + background-color: darken($color, 10%) !important; + } + } + @include deprecate("The `bg-variant` mixin", "v4.4.0", "v5", $ignore-warning); +} + +@mixin bg-gradient-variant($parent, $color, $ignore-warning: false) { + #{$parent} { + background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x !important; + } + @include deprecate("The `bg-gradient-variant` mixin", "v4.5.0", "v5", $ignore-warning); +} diff --git a/src/theme/components/bootstrap/mixins/_badge.scss b/src/theme/components/bootstrap/mixins/_badge.scss new file mode 100644 index 000000000..f1c499141 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_badge.scss @@ -0,0 +1,17 @@ +@mixin badge-variant($bg) { + color: color-yiq($bg); + background-color: $bg; + + @at-root a#{&} { + @include hover-focus() { + color: color-yiq($bg); + background-color: darken($bg, 10%); + } + + &:focus, + &.focus { + outline: 0; + box-shadow: 0 0 0 $badge-focus-width rgba($bg, .5); + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_border-radius.scss b/src/theme/components/bootstrap/mixins/_border-radius.scss new file mode 100644 index 000000000..90c6e1451 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_border-radius.scss @@ -0,0 +1,76 @@ +// stylelint-disable property-disallowed-list +// Single side border-radius + +// Helper function to replace negative values with 0 +@function valid-radius($radius) { + $return: (); + @each $value in $radius { + @if type-of($value) == number { + $return: append($return, max($value, 0)); + } @else { + $return: append($return, $value); + } + } + @return $return; + } + + @mixin border-radius($radius: $border-radius, $fallback-border-radius: false) { + @if $enable-rounded { + border-radius: valid-radius($radius); + } + @else if $fallback-border-radius != false { + border-radius: $fallback-border-radius; + } + } + + @mixin border-top-radius($radius) { + @if $enable-rounded { + border-top-left-radius: valid-radius($radius); + border-top-right-radius: valid-radius($radius); + } + } + + @mixin border-right-radius($radius) { + @if $enable-rounded { + border-top-right-radius: valid-radius($radius); + border-bottom-right-radius: valid-radius($radius); + } + } + + @mixin border-bottom-radius($radius) { + @if $enable-rounded { + border-bottom-right-radius: valid-radius($radius); + border-bottom-left-radius: valid-radius($radius); + } + } + + @mixin border-left-radius($radius) { + @if $enable-rounded { + border-top-left-radius: valid-radius($radius); + border-bottom-left-radius: valid-radius($radius); + } + } + + @mixin border-top-left-radius($radius) { + @if $enable-rounded { + border-top-left-radius: valid-radius($radius); + } + } + + @mixin border-top-right-radius($radius) { + @if $enable-rounded { + border-top-right-radius: valid-radius($radius); + } + } + + @mixin border-bottom-right-radius($radius) { + @if $enable-rounded { + border-bottom-right-radius: valid-radius($radius); + } + } + + @mixin border-bottom-left-radius($radius) { + @if $enable-rounded { + border-bottom-left-radius: valid-radius($radius); + } + } diff --git a/src/theme/components/bootstrap/mixins/_box-shadow.scss b/src/theme/components/bootstrap/mixins/_box-shadow.scss new file mode 100644 index 000000000..0726d4359 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_box-shadow.scss @@ -0,0 +1,20 @@ +@mixin box-shadow($shadow...) { + @if $enable-shadows { + $result: (); + + @if (length($shadow) == 1) { + // We can pass `@include box-shadow(none);` + $result: $shadow; + } @else { + // Filter to avoid invalid properties for example `box-shadow: none, 1px 1px black;` + @for $i from 1 through length($shadow) { + @if nth($shadow, $i) != "none" { + $result: append($result, nth($shadow, $i), "comma"); + } + } + } + @if (length($result) > 0) { + box-shadow: $result; + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_breakpoints.scss b/src/theme/components/bootstrap/mixins/_breakpoints.scss new file mode 100644 index 000000000..23a5de96b --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_breakpoints.scss @@ -0,0 +1,123 @@ +// 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 `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default. + +// 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: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) { + $n: index($breakpoint-names, $name); + @return if($n != null and $n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); +} + +// 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: $grid-breakpoints) { + $min: map-get($breakpoints, $name); + @return if($min != 0, $min, null); +} + +// Maximum breakpoint width. Null for the largest (last) breakpoint. +// The maximum value is calculated as the minimum of the next one less 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. +// See https://bugs.webkit.org/show_bug.cgi?id=178261 +// +// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) +// 767.98px +@function breakpoint-max($name, $breakpoints: $grid-breakpoints) { + $next: breakpoint-next($name, $breakpoints); + @return if($next, breakpoint-min($next, $breakpoints) - .02, null); +} + +// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front. +// 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: $grid-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: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + @if $min { + @media (min-width: $min) { + @content; + } + } @else { + @content; + } +} + +// 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: $grid-breakpoints) { + $max: breakpoint-max($name, $breakpoints); + @if $max { + @media (max-width: $max) { + @content; + } + } @else { + @content; + } +} + +// Media that spans multiple breakpoint widths. +// Makes the @content apply between the min and max breakpoints +@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($lower, $breakpoints); + $max: breakpoint-max($upper, $breakpoints); + + @if $min != null and $max != null { + @media (min-width: $min) and (max-width: $max) { + @content; + } + } @else if $max == null { + @include media-breakpoint-up($lower, $breakpoints) { + @content; + } + } @else if $min == null { + @include media-breakpoint-down($upper, $breakpoints) { + @content; + } + } +} + +// Media between the breakpoint's minimum and maximum widths. +// No minimum for the smallest breakpoint, and no maximum for the largest one. +// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower. +@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + $max: breakpoint-max($name, $breakpoints); + + @if $min != null and $max != null { + @media (min-width: $min) and (max-width: $max) { + @content; + } + } @else if $max == null { + @include media-breakpoint-up($name, $breakpoints) { + @content; + } + } @else if $min == null { + @include media-breakpoint-down($name, $breakpoints) { + @content; + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_buttons.scss b/src/theme/components/bootstrap/mixins/_buttons.scss new file mode 100644 index 000000000..d6235aa27 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_buttons.scss @@ -0,0 +1,110 @@ +// Button variants +// +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons + +@mixin button-variant($background, $border, $hover-background: darken($background, 7.5%), $hover-border: darken($border, 10%), $active-background: darken($background, 10%), $active-border: darken($border, 12.5%)) { + color: color-yiq($background); + @include gradient-bg($background); + border-color: $border; + @include box-shadow($btn-box-shadow); + + @include hover() { + color: color-yiq($hover-background); + @include gradient-bg($hover-background); + border-color: $hover-border; + } + + &:focus, + &.focus { + color: color-yiq($hover-background); + @include gradient-bg($hover-background); + border-color: $hover-border; + @if $enable-shadows { + @include box-shadow($btn-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5)); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5); + } + } + + // Disabled comes first so active can properly restyle + &.disabled, + &:disabled { + color: color-yiq($background); + background-color: $background; + border-color: $border; + // Remove CSS gradients if they're enabled + @if $enable-gradients { + background-image: none; + } + } + + &:not(:disabled):not(.disabled):active, + &:not(:disabled):not(.disabled).active, + .show > &.dropdown-toggle { + color: color-yiq($active-background); + background-color: $active-background; + @if $enable-gradients { + background-image: none; // Remove the gradient for the pressed/active state + } + border-color: $active-border; + + &:focus { + @if $enable-shadows and $btn-active-box-shadow != none { + @include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5)); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5); + } + } + } +} + +@mixin button-outline-variant($color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color) { + color: $color; + border-color: $color; + + @include hover() { + color: $color-hover; + background-color: $active-background; + border-color: $active-border; + } + + &:focus, + &.focus { + box-shadow: 0 0 0 $btn-focus-width rgba($color, .5); + } + + &.disabled, + &:disabled { + color: $color; + background-color: transparent; + } + + &:not(:disabled):not(.disabled):active, + &:not(:disabled):not(.disabled).active, + .show > &.dropdown-toggle { + color: color-yiq($active-background); + background-color: $active-background; + border-color: $active-border; + + &:focus { + @if $enable-shadows and $btn-active-box-shadow != none { + @include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba($color, .5)); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: 0 0 0 $btn-focus-width rgba($color, .5); + } + } + } +} + +// Button sizes +@mixin button-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) { + padding: $padding-y $padding-x; + @include font-size($font-size); + line-height: $line-height; + // Manually declare to provide an override to the browser default + @include border-radius($border-radius, 0); +} diff --git a/src/theme/components/bootstrap/mixins/_caret.scss b/src/theme/components/bootstrap/mixins/_caret.scss new file mode 100644 index 000000000..27466495b --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_caret.scss @@ -0,0 +1,62 @@ +@mixin caret-down() { + border-top: $caret-width solid; + border-right: $caret-width solid transparent; + border-bottom: 0; + border-left: $caret-width solid transparent; +} + +@mixin caret-up() { + border-top: 0; + border-right: $caret-width solid transparent; + border-bottom: $caret-width solid; + border-left: $caret-width solid transparent; +} + +@mixin caret-right() { + border-top: $caret-width solid transparent; + border-right: 0; + border-bottom: $caret-width solid transparent; + border-left: $caret-width solid; +} + +@mixin caret-left() { + border-top: $caret-width solid transparent; + border-right: $caret-width solid; + border-bottom: $caret-width solid transparent; +} + +@mixin caret($direction: down) { + @if $enable-caret { + &::after { + display: inline-block; + margin-left: $caret-spacing; + vertical-align: $caret-vertical-align; + content: ""; + @if $direction == down { + @include caret-down(); + } @else if $direction == up { + @include caret-up(); + } @else if $direction == right { + @include caret-right(); + } + } + + @if $direction == left { + &::after { + display: none; + } + + &::before { + display: inline-block; + margin-right: $caret-spacing; + vertical-align: $caret-vertical-align; + content: ""; + @include caret-left(); + } + } + + &:empty::after { + margin-left: 0; + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_clearfix.scss b/src/theme/components/bootstrap/mixins/_clearfix.scss new file mode 100644 index 000000000..11a977b73 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_clearfix.scss @@ -0,0 +1,7 @@ +@mixin clearfix() { + &::after { + display: block; + clear: both; + content: ""; + } +} diff --git a/src/theme/components/bootstrap/mixins/_deprecate.scss b/src/theme/components/bootstrap/mixins/_deprecate.scss new file mode 100644 index 000000000..df070bc59 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_deprecate.scss @@ -0,0 +1,10 @@ +// Deprecate mixin +// +// This mixin can be used to deprecate mixins or functions. +// `$enable-deprecation-messages` is a global variable, `$ignore-warning` is a variable that can be passed to +// some deprecated mixins to suppress the warning (for example if the mixin is still be used in the current version of Bootstrap) +@mixin deprecate($name, $deprecate-version, $remove-version, $ignore-warning: false) { + @if ($enable-deprecation-messages != false and $ignore-warning != true) { + @warn "#{$name} has been deprecated as of #{$deprecate-version}. It will be removed entirely in #{$remove-version}."; + } +} diff --git a/src/theme/components/bootstrap/mixins/_float.scss b/src/theme/components/bootstrap/mixins/_float.scss new file mode 100644 index 000000000..6b376a258 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_float.scss @@ -0,0 +1,14 @@ +// stylelint-disable declaration-no-important + +@mixin float-left() { + float: left !important; + @include deprecate("The `float-left` mixin", "v4.3.0", "v5"); +} +@mixin float-right() { + float: right !important; + @include deprecate("The `float-right` mixin", "v4.3.0", "v5"); +} +@mixin float-none() { + float: none !important; + @include deprecate("The `float-none` mixin", "v4.3.0", "v5"); +} diff --git a/src/theme/components/bootstrap/mixins/_forms.scss b/src/theme/components/bootstrap/mixins/_forms.scss new file mode 100644 index 000000000..b8847cc80 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_forms.scss @@ -0,0 +1,195 @@ +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `$input-focus-border-color` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. +@mixin form-control-focus($ignore-warning: false) { + &:focus { + color: $input-focus-color; + background-color: $input-focus-bg; + border-color: $input-focus-border-color; + outline: 0; + @if $enable-shadows { + @include box-shadow($input-box-shadow, $input-focus-box-shadow); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $input-focus-box-shadow; + } + } + @include deprecate("The `form-control-focus()` mixin", "v4.4.0", "v5", $ignore-warning); +} + +// This mixin uses an `if()` technique to be compatible with Dart Sass +// See https://github.com/sass/sass/issues/1873#issuecomment-152293725 for more details +@mixin form-validation-state-selector($state) { + @if ($state == "valid" or $state == "invalid") { + .was-validated #{if(&, "&", "")}:#{$state}, + #{if(&, "&", "")}.is-#{$state} { + @content; + } + } @else { + #{if(&, "&", "")}.is-#{$state} { + @content; + } + } +} + +@mixin form-validation-state($state, $color, $icon) { + .#{$state}-feedback { + display: none; + width: 100%; + margin-top: $form-feedback-margin-top; + @include font-size($form-feedback-font-size); + color: $color; + } + + .#{$state}-tooltip { + position: absolute; + top: 100%; + left: 0; + z-index: 5; + display: none; + max-width: 100%; // Contain to parent when possible + padding: $form-feedback-tooltip-padding-y $form-feedback-tooltip-padding-x; + margin-top: .1rem; + @include font-size($form-feedback-tooltip-font-size); + line-height: $form-feedback-tooltip-line-height; + color: color-yiq($color); + background-color: rgba($color, $form-feedback-tooltip-opacity); + @include border-radius($form-feedback-tooltip-border-radius); + + // See https://github.com/twbs/bootstrap/pull/31557 + // Align tooltip to form elements + .form-row > .col > &, + .form-row > [class*="col-"] > & { + left: $form-grid-gutter-width * .5; + } + } + + @include form-validation-state-selector($state) { + ~ .#{$state}-feedback, + ~ .#{$state}-tooltip { + display: block; + } + } + + .form-control { + @include form-validation-state-selector($state) { + border-color: $color; + + @if $enable-validation-icons { + padding-right: $input-height-inner !important; // stylelint-disable-line declaration-no-important + background-image: escape-svg($icon); + background-repeat: no-repeat; + background-position: right $input-height-inner-quarter center; + background-size: $input-height-inner-half $input-height-inner-half; + } + + &:focus { + border-color: $color; + box-shadow: 0 0 0 $input-focus-width rgba($color, .25); + } + } + } + + // stylelint-disable-next-line selector-no-qualifying-type + select.form-control { + @include form-validation-state-selector($state) { + @if $enable-validation-icons { + padding-right: $input-padding-x * 4 !important; // stylelint-disable-line declaration-no-important + background-position: right $input-padding-x * 2 center; + } + } + } + + // stylelint-disable-next-line selector-no-qualifying-type + textarea.form-control { + @include form-validation-state-selector($state) { + @if $enable-validation-icons { + padding-right: $input-height-inner; + background-position: top $input-height-inner-quarter right $input-height-inner-quarter; + } + } + } + + .custom-select { + @include form-validation-state-selector($state) { + border-color: $color; + + @if $enable-validation-icons { + padding-right: $custom-select-feedback-icon-padding-right !important; // stylelint-disable-line declaration-no-important + background: $custom-select-background, $custom-select-bg escape-svg($icon) $custom-select-feedback-icon-position / $custom-select-feedback-icon-size no-repeat; + } + + &:focus { + border-color: $color; + box-shadow: 0 0 0 $input-focus-width rgba($color, .25); + } + } + } + + .form-check-input { + @include form-validation-state-selector($state) { + ~ .form-check-label { + color: $color; + } + + ~ .#{$state}-feedback, + ~ .#{$state}-tooltip { + display: block; + } + } + } + + .custom-control-input { + @include form-validation-state-selector($state) { + ~ .custom-control-label { + color: $color; + + &::before { + border-color: $color; + } + } + + &:checked { + ~ .custom-control-label::before { + border-color: lighten($color, 10%); + @include gradient-bg(lighten($color, 10%)); + } + } + + &:focus { + ~ .custom-control-label::before { + box-shadow: 0 0 0 $input-focus-width rgba($color, .25); + } + + &:not(:checked) ~ .custom-control-label::before { + border-color: $color; + } + } + } + } + + // custom file + .custom-file-input { + @include form-validation-state-selector($state) { + ~ .custom-file-label { + border-color: $color; + } + + &:focus { + ~ .custom-file-label { + border-color: $color; + box-shadow: 0 0 0 $input-focus-width rgba($color, .25); + } + } + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_gradients.scss b/src/theme/components/bootstrap/mixins/_gradients.scss new file mode 100644 index 000000000..88c4d64b7 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_gradients.scss @@ -0,0 +1,45 @@ +// Gradients + +@mixin gradient-bg($color) { + @if $enable-gradients { + background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x; + } @else { + background-color: $color; + } +} + +// Horizontal gradient, from left to right +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +@mixin gradient-x($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) { + background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); + background-repeat: repeat-x; +} + +// Vertical gradient, from top to bottom +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +@mixin gradient-y($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) { + background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); + background-repeat: repeat-x; +} + +@mixin gradient-directional($start-color: $gray-700, $end-color: $gray-800, $deg: 45deg) { + background-image: linear-gradient($deg, $start-color, $end-color); + background-repeat: repeat-x; +} +@mixin gradient-x-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) { + background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; +} +@mixin gradient-y-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) { + background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; +} +@mixin gradient-radial($inner-color: $gray-700, $outer-color: $gray-800) { + background-image: radial-gradient(circle, $inner-color, $outer-color); + background-repeat: no-repeat; +} +@mixin gradient-striped($color: rgba($white, .15), $angle: 45deg) { + background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); +} diff --git a/src/theme/components/bootstrap/mixins/_grid copy.scss b/src/theme/components/bootstrap/mixins/_grid copy.scss new file mode 100644 index 000000000..0eb09910d --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_grid copy.scss @@ -0,0 +1,69 @@ +/// Grid system +// +// Generate semantic grid columns with these mixins. + +@mixin make-container($gutter: $grid-gutter-width) { + width: 100%; + padding-right: $gutter * .5; + padding-left: $gutter * .5; + margin-right: auto; + margin-left: auto; +} + +@mixin make-row($gutter: $grid-gutter-width) { + display: flex; + flex-wrap: wrap; + margin-right: -$gutter * .5; + margin-left: -$gutter * .5; +} + +// For each breakpoint, define the maximum width of the container in a media query +@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) { + @each $breakpoint, $container-max-width in $max-widths { + @include media-breakpoint-up($breakpoint, $breakpoints) { + max-width: $container-max-width; + } + } + @include deprecate("The `make-container-max-widths` mixin", "v4.5.2", "v5"); +} + +@mixin make-col-ready($gutter: $grid-gutter-width) { + position: relative; + // Prevent columns from becoming too narrow when at smaller grid tiers by + // always setting `width: 100%;`. This works because we use `flex` values + // later on to override this initial width. + width: 100%; + padding-right: $gutter * .5; + padding-left: $gutter * .5; +} + +@mixin make-col($size, $columns: $grid-columns) { + flex: 0 0 percentage(divide($size, $columns)); + // Add a `max-width` to ensure content within each column does not blow out + // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari + // do not appear to require this. + max-width: percentage(divide($size, $columns)); +} + +@mixin make-col-auto() { + flex: 0 0 auto; + width: auto; + max-width: 100%; // Reset earlier grid tiers +} + +@mixin make-col-offset($size, $columns: $grid-columns) { + $num: divide($size, $columns); + margin-left: if($num == 0, 0, percentage($num)); +} + +// Row columns +// +// Specify on a parent element(e.g., .row) to force immediate children into NN +// numberof columns. Supports wrapping to new lines, but does not do a Masonry +// style grid. +@mixin row-cols($count) { + > * { + flex: 0 0 divide(100%, $count); + max-width: divide(100%, $count); + } +} diff --git a/src/theme/components/bootstrap/mixins/_grid-framework.scss b/src/theme/components/bootstrap/mixins/_grid-framework.scss new file mode 100644 index 000000000..ef3291753 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_grid-framework.scss @@ -0,0 +1,80 @@ +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `$grid-columns`. + +@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) { + // Common properties for all breakpoints + %grid-column { + position: relative; + width: 100%; + padding-right: $gutter * .5; + padding-left: $gutter * .5; + } + + @each $breakpoint in map-keys($breakpoints) { + $infix: breakpoint-infix($breakpoint, $breakpoints); + + @if $columns > 0 { + // Allow columns to stretch full width below their breakpoints + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + @extend %grid-column; + } + } + } + + .col#{$infix}, + .col#{$infix}-auto { + @extend %grid-column; + } + + @include media-breakpoint-up($breakpoint, $breakpoints) { + // Provide basic `.col-{bp}` classes for equal-width flexbox columns + .col#{$infix} { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; + } + + @if $grid-row-columns > 0 { + @for $i from 1 through $grid-row-columns { + .row-cols#{$infix}-#{$i} { + @include row-cols($i); + } + } + } + + .col#{$infix}-auto { + @include make-col-auto(); + } + + @if $columns > 0 { + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + @include make-col($i, $columns); + } + } + } + + .order#{$infix}-first { order: -1; } + + .order#{$infix}-last { order: $columns + 1; } + + @for $i from 0 through $columns { + .order#{$infix}-#{$i} { order: $i; } + } + + @if $columns > 0 { + // `$columns - 1` because offsetting by the width of an entire row isn't possible + @for $i from 0 through ($columns - 1) { + @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-0 + .offset#{$infix}-#{$i} { + @include make-col-offset($i, $columns); + } + } + } + } + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_grid.scss b/src/theme/components/bootstrap/mixins/_grid.scss new file mode 100644 index 000000000..0eb09910d --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_grid.scss @@ -0,0 +1,69 @@ +/// Grid system +// +// Generate semantic grid columns with these mixins. + +@mixin make-container($gutter: $grid-gutter-width) { + width: 100%; + padding-right: $gutter * .5; + padding-left: $gutter * .5; + margin-right: auto; + margin-left: auto; +} + +@mixin make-row($gutter: $grid-gutter-width) { + display: flex; + flex-wrap: wrap; + margin-right: -$gutter * .5; + margin-left: -$gutter * .5; +} + +// For each breakpoint, define the maximum width of the container in a media query +@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) { + @each $breakpoint, $container-max-width in $max-widths { + @include media-breakpoint-up($breakpoint, $breakpoints) { + max-width: $container-max-width; + } + } + @include deprecate("The `make-container-max-widths` mixin", "v4.5.2", "v5"); +} + +@mixin make-col-ready($gutter: $grid-gutter-width) { + position: relative; + // Prevent columns from becoming too narrow when at smaller grid tiers by + // always setting `width: 100%;`. This works because we use `flex` values + // later on to override this initial width. + width: 100%; + padding-right: $gutter * .5; + padding-left: $gutter * .5; +} + +@mixin make-col($size, $columns: $grid-columns) { + flex: 0 0 percentage(divide($size, $columns)); + // Add a `max-width` to ensure content within each column does not blow out + // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari + // do not appear to require this. + max-width: percentage(divide($size, $columns)); +} + +@mixin make-col-auto() { + flex: 0 0 auto; + width: auto; + max-width: 100%; // Reset earlier grid tiers +} + +@mixin make-col-offset($size, $columns: $grid-columns) { + $num: divide($size, $columns); + margin-left: if($num == 0, 0, percentage($num)); +} + +// Row columns +// +// Specify on a parent element(e.g., .row) to force immediate children into NN +// numberof columns. Supports wrapping to new lines, but does not do a Masonry +// style grid. +@mixin row-cols($count) { + > * { + flex: 0 0 divide(100%, $count); + max-width: divide(100%, $count); + } +} diff --git a/src/theme/components/bootstrap/mixins/_hover.scss b/src/theme/components/bootstrap/mixins/_hover.scss new file mode 100644 index 000000000..409f8244e --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_hover.scss @@ -0,0 +1,37 @@ +// Hover mixin and `$enable-hover-media-query` are deprecated. +// +// Originally added during our alphas and maintained during betas, this mixin was +// designed to prevent `:hover` stickiness on iOS-an issue where hover styles +// would persist after initial touch. +// +// For backward compatibility, we've kept these mixins and updated them to +// always return their regular pseudo-classes instead of a shimmed media query. +// +// Issue: https://github.com/twbs/bootstrap/issues/25195 + +@mixin hover() { + &:hover { @content; } +} + +@mixin hover-focus() { + &:hover, + &:focus { + @content; + } +} + +@mixin plain-hover-focus() { + &, + &:hover, + &:focus { + @content; + } +} + +@mixin hover-focus-active() { + &:hover, + &:focus, + &:active { + @content; + } +} diff --git a/src/theme/components/bootstrap/mixins/_image.scss b/src/theme/components/bootstrap/mixins/_image.scss new file mode 100644 index 000000000..3aaa0d704 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_image.scss @@ -0,0 +1,36 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. + +@mixin img-fluid() { + // Part 1: Set a maximum relative to the parent + max-width: 100%; + // Part 2: Override the height to auto, otherwise images will be stretched + // when setting a width and height attribute on the img element. + height: auto; +} + + +// Retina image +// +// Short retina mixin for setting background-image and -size. + +@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) { + background-image: url($file-1x); + + // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio, + // but doesn't convert dppx=>dpi. + // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard. + // Compatibility info: https://caniuse.com/css-media-resolution + @media only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx + only screen and (min-resolution: 2dppx) { // Standardized + background-image: url($file-2x); + background-size: $width-1x $height-1x; + } + @include deprecate("`img-retina()`", "v4.3.0", "v5"); +} diff --git a/src/theme/components/bootstrap/mixins/_list-group.scss b/src/theme/components/bootstrap/mixins/_list-group.scss new file mode 100644 index 000000000..0da353156 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_list-group.scss @@ -0,0 +1,21 @@ +// List Groups + +@mixin list-group-item-variant($state, $background, $color) { + .list-group-item-#{$state} { + color: $color; + background-color: $background; + + &.list-group-item-action { + @include hover-focus() { + color: $color; + background-color: darken($background, 5%); + } + + &.active { + color: $white; + background-color: $color; + border-color: $color; + } + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_lists.scss b/src/theme/components/bootstrap/mixins/_lists.scss new file mode 100644 index 000000000..251cb0733 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_lists.scss @@ -0,0 +1,7 @@ +// Lists + +// Unstyled keeps list items block level, just removes default browser padding and list-style +@mixin list-unstyled() { + padding-left: 0; + list-style: none; +} diff --git a/src/theme/components/bootstrap/mixins/_nav-divider.scss b/src/theme/components/bootstrap/mixins/_nav-divider.scss new file mode 100644 index 000000000..3e0cceafe --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_nav-divider.scss @@ -0,0 +1,11 @@ +// Horizontal dividers +// +// Dividers (basically an hr) within dropdowns and nav lists + +@mixin nav-divider($color: $nav-divider-color, $margin-y: $nav-divider-margin-y, $ignore-warning: false) { + height: 0; + margin: $margin-y 0; + overflow: hidden; + border-top: 1px solid $color; + @include deprecate("The `nav-divider()` mixin", "v4.4.0", "v5", $ignore-warning); +} diff --git a/src/theme/components/bootstrap/mixins/_pagination.scss b/src/theme/components/bootstrap/mixins/_pagination.scss new file mode 100644 index 000000000..af8e16d6a --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_pagination.scss @@ -0,0 +1,22 @@ +// Pagination + +@mixin pagination-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) { + .page-link { + padding: $padding-y $padding-x; + @include font-size($font-size); + line-height: $line-height; + } + + .page-item { + &:first-child { + .page-link { + @include border-left-radius($border-radius); + } + } + &:last-child { + .page-link { + @include border-right-radius($border-radius); + } + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_reset-text.scss b/src/theme/components/bootstrap/mixins/_reset-text.scss new file mode 100644 index 000000000..868253307 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_reset-text.scss @@ -0,0 +1,17 @@ +@mixin reset-text() { + font-family: $font-family-base; + // We deliberately do NOT reset font-size or word-wrap. + font-style: normal; + font-weight: $font-weight-normal; + line-height: $line-height-base; + text-align: left; // Fallback for where `start` is not supported + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; +} diff --git a/src/theme/components/bootstrap/mixins/_resize.scss b/src/theme/components/bootstrap/mixins/_resize.scss new file mode 100644 index 000000000..66f233a63 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_resize.scss @@ -0,0 +1,6 @@ +// Resize anything + +@mixin resizable($direction) { + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` + resize: $direction; // Options: horizontal, vertical, both +} diff --git a/src/theme/components/bootstrap/mixins/_screen-reader.scss b/src/theme/components/bootstrap/mixins/_screen-reader.scss new file mode 100644 index 000000000..70b677e3f --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_screen-reader.scss @@ -0,0 +1,34 @@ +// Only display content to screen readers +// +// See: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ +// See: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/ + +@mixin sr-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; // Fix for https://github.com/twbs/bootstrap/issues/25686 + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +@mixin sr-only-focusable() { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; + } +} diff --git a/src/theme/components/bootstrap/mixins/_size.scss b/src/theme/components/bootstrap/mixins/_size.scss new file mode 100644 index 000000000..69e056d2c --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_size.scss @@ -0,0 +1,7 @@ +// Sizing shortcuts + +@mixin size($width, $height: $width) { + width: $width; + height: $height; + @include deprecate("`size()`", "v4.3.0", "v5"); +} diff --git a/src/theme/components/bootstrap/mixins/_table-row.scss b/src/theme/components/bootstrap/mixins/_table-row.scss new file mode 100644 index 000000000..1ccde6b6c --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_table-row.scss @@ -0,0 +1,39 @@ +// Tables + +@mixin table-row-variant($state, $background, $border: null) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table-#{$state} { + &, + > th, + > td { + background-color: $background; + } + + @if $border != null { + th, + td, + thead th, + tbody + tbody { + border-color: $border; + } + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover { + $hover-background: darken($background, 5%); + + .table-#{$state} { + @include hover() { + background-color: $hover-background; + + > td, + > th { + background-color: $hover-background; + } + } + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_text-emphasis.scss b/src/theme/components/bootstrap/mixins/_text-emphasis.scss new file mode 100644 index 000000000..5eb8a5515 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_text-emphasis.scss @@ -0,0 +1,17 @@ +// stylelint-disable declaration-no-important + +// Typography + +@mixin text-emphasis-variant($parent, $color, $ignore-warning: false) { + #{$parent} { + color: $color !important; + } + @if $emphasized-link-hover-darken-percentage != 0 { + a#{$parent} { + @include hover-focus() { + color: darken($color, $emphasized-link-hover-darken-percentage) !important; + } + } + } + @include deprecate("`text-emphasis-variant()`", "v4.4.0", "v5", $ignore-warning); +} diff --git a/src/theme/components/bootstrap/mixins/_text-hide.scss b/src/theme/components/bootstrap/mixins/_text-hide.scss new file mode 100644 index 000000000..3a923011e --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_text-hide.scss @@ -0,0 +1,11 @@ +// CSS image replacement +@mixin text-hide($ignore-warning: false) { + // stylelint-disable-next-line font-family-no-missing-generic-family-keyword + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; + + @include deprecate("`text-hide()`", "v4.1.0", "v5", $ignore-warning); +} diff --git a/src/theme/components/bootstrap/mixins/_text-truncate.scss b/src/theme/components/bootstrap/mixins/_text-truncate.scss new file mode 100644 index 000000000..3504bb1aa --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_text-truncate.scss @@ -0,0 +1,8 @@ +// Text truncate +// Requires inline-block or block for proper styling + +@mixin text-truncate() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/theme/components/bootstrap/mixins/_transition.scss b/src/theme/components/bootstrap/mixins/_transition.scss new file mode 100644 index 000000000..54870bf6a --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_transition.scss @@ -0,0 +1,26 @@ +// stylelint-disable property-disallowed-list +@mixin transition($transition...) { + @if length($transition) == 0 { + $transition: $transition-base; + } + + @if length($transition) > 1 { + @each $value in $transition { + @if $value == null or $value == none { + @warn "The keyword 'none' or 'null' must be used as a single argument."; + } + } + } + + @if $enable-transitions { + @if nth($transition, 1) != null { + transition: $transition; + } + + @if $enable-prefers-reduced-motion-media-query and nth($transition, 1) != null and nth($transition, 1) != none { + @media (prefers-reduced-motion: reduce) { + transition: none; + } + } + } +} diff --git a/src/theme/components/bootstrap/mixins/_visibility.scss b/src/theme/components/bootstrap/mixins/_visibility.scss new file mode 100644 index 000000000..f17467311 --- /dev/null +++ b/src/theme/components/bootstrap/mixins/_visibility.scss @@ -0,0 +1,8 @@ +// stylelint-disable declaration-no-important + +// Visibility + +@mixin invisible($visibility) { + visibility: $visibility !important; + @include deprecate("`invisible()`", "v4.3.0", "v5"); +} diff --git a/src/theme/components/bootstrap/utilities/_align.scss b/src/theme/components/bootstrap/utilities/_align.scss new file mode 100644 index 000000000..8b7df9f76 --- /dev/null +++ b/src/theme/components/bootstrap/utilities/_align.scss @@ -0,0 +1,8 @@ +// stylelint-disable declaration-no-important + +.align-baseline { vertical-align: baseline !important; } // Browser default +.align-top { vertical-align: top !important; } +.align-middle { vertical-align: middle !important; } +.align-bottom { vertical-align: bottom !important; } +.align-text-bottom { vertical-align: text-bottom !important; } +.align-text-top { vertical-align: text-top !important; } diff --git a/src/theme/components/bootstrap/utilities/_borders.scss b/src/theme/components/bootstrap/utilities/_borders.scss new file mode 100644 index 000000000..d947e3b5a --- /dev/null +++ b/src/theme/components/bootstrap/utilities/_borders.scss @@ -0,0 +1,69 @@ +.border { border: 1px solid var(--gray-500) !important; } +.border-top { border-top: 1px solid var(--gray-500) !important; } +.border-right { border-right: 1px solid var(--gray-500) !important; } +.border-bottom { border-bottom: 1px solid var(--gray-500) !important; } +.border-left { border-left: 1px solid var(--gray-500) !important; } + +.border-0 { border: 0 !important; } +.border-top-0 { border-top: 0 !important; } +.border-right-0 { border-right: 0 !important; } +.border-bottom-0 { border-bottom: 0 !important; } +.border-left-0 { border-left: 0 !important; } + +@each $color-name, $unused in global.$colors { + .border-#{$color-name} { + border-color: var(--#{$color-name}) !important; + } +} + +.border-white { + border-color: var(--white) !important; +} + +// +// Border-radius +// + +.rounded-sm { + border-radius: var(--mdl-shape-borderRadius-sm) !important; +} + +.rounded { + border-radius: var(--mdl-shape-borderRadius-md) !important; +} + +.rounded-top { + border-top-left-radius: var(--mdl-shape-borderRadius-md) !important; + border-top-right-radius: var(--mdl-shape-borderRadius-md) !important; +} + +.rounded-right { + border-top-right-radius: var(--mdl-shape-borderRadius-md) !important; + border-bottom-right-radius: var(--mdl-shape-borderRadius-md) !important; +} + +.rounded-bottom { + border-bottom-right-radius: var(--mdl-shape-borderRadius-md) !important; + border-bottom-left-radius: var(--mdl-shape-borderRadius-md) !important; +} + +.rounded-left { + border-top-left-radius: var(--mdl-shape-borderRadius-md) !important; + border-bottom-left-radius: var(--mdl-shape-borderRadius-md) !important; +} + +.rounded-lg { + border-radius: var(--mdl-shape-borderRadius-lg) !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-pill { + border-radius: 50rem !important; +} + +.rounded-0 { + border-radius: 0 !important; +} diff --git a/src/theme/components/bootstrap/utilities/_flex.scss b/src/theme/components/bootstrap/utilities/_flex.scss new file mode 100644 index 000000000..f1639e312 --- /dev/null +++ b/src/theme/components/bootstrap/utilities/_flex.scss @@ -0,0 +1,39 @@ +.flex-row { flex-direction: row !important; } +.flex-column { flex-direction: column !important; } +.flex-row-reverse { flex-direction: row-reverse !important; } +.flex-column-reverse { flex-direction: column-reverse !important; } + +.flex-wrap { flex-wrap: wrap !important; } +.flex-nowrap { flex-wrap: nowrap !important; } +.flex-wrap-reverse { flex-wrap: wrap-reverse !important; } +.flex-fill { flex: 1 1 auto !important; } +.flex-grow-0 { flex-grow: 0 !important; } +.flex-grow-1 { flex-grow: 1 !important; } +.flex-shrink-0 { flex-shrink: 0 !important; } +.flex-shrink-1 { flex-shrink: 1 !important; } + +.justify-content-start { justify-content: flex-start !important; } +.justify-content-end { justify-content: flex-end !important; } +.justify-content-center { justify-content: center !important; } +.justify-content-between { justify-content: space-between !important; } +.justify-content-around { justify-content: space-around !important; } + +.align-items-start { align-items: flex-start !important; } +.align-items-end { align-items: flex-end !important; } +.align-items-center { align-items: center !important; } +.align-items-baseline { align-items: baseline !important; } +.align-items-stretch { align-items: stretch !important; } + +.align-content-start { align-content: flex-start !important; } +.align-content-end { align-content: flex-end !important; } +.align-content-center { align-content: center !important; } +.align-content-between { align-content: space-between !important; } +.align-content-around { align-content: space-around !important; } +.align-content-stretch { align-content: stretch !important; } + +.align-self-auto { align-self: auto !important; } +.align-self-start { align-self: flex-start !important; } +.align-self-end { align-self: flex-end !important; } +.align-self-center { align-self: center !important; } +.align-self-baseline { align-self: baseline !important; } +.align-self-stretch { align-self: stretch !important; } diff --git a/src/theme/bootstrap.scss b/src/theme/components/bootstrap/utilities/_screenreaders.scss similarity index 84% rename from src/theme/bootstrap.scss rename to src/theme/components/bootstrap/utilities/_screenreaders.scss index 673073c71..1296bf9bc 100644 --- a/src/theme/bootstrap.scss +++ b/src/theme/components/bootstrap/utilities/_screenreaders.scss @@ -1,4 +1,7 @@ -// Text for accessibility, hidden from the view. +// +// Screenreaders +// + .sr-only, .accesshide { @include sr-only(); diff --git a/src/theme/components/bootstrap/utilities/_spacing.scss b/src/theme/components/bootstrap/utilities/_spacing.scss new file mode 100644 index 000000000..3e98581a9 --- /dev/null +++ b/src/theme/components/bootstrap/utilities/_spacing.scss @@ -0,0 +1,73 @@ +// stylelint-disable declaration-no-important + +// Margin and Padding + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + @each $prop, $abbrev in (margin: m, padding: p) { + @each $size, $length in $spacers { + .#{$abbrev}#{$infix}-#{$size} { #{$prop}: $length !important; } + .#{$abbrev}t#{$infix}-#{$size}, + .#{$abbrev}y#{$infix}-#{$size} { + #{$prop}-top: $length !important; + } + .#{$abbrev}r#{$infix}-#{$size}, + .#{$abbrev}x#{$infix}-#{$size} { + #{$prop}-right: $length !important; + } + .#{$abbrev}b#{$infix}-#{$size}, + .#{$abbrev}y#{$infix}-#{$size} { + #{$prop}-bottom: $length !important; + } + .#{$abbrev}l#{$infix}-#{$size}, + .#{$abbrev}x#{$infix}-#{$size} { + #{$prop}-left: $length !important; + } + } + } + + // Negative margins (e.g., where `.mb-n1` is negative version of `.mb-1`) + @each $size, $length in $spacers { + @if "#{$size}" != "0" { + .m#{$infix}-n#{$size} { margin: -$length !important; } + .mt#{$infix}-n#{$size}, + .my#{$infix}-n#{$size} { + margin-top: -$length !important; + } + .mr#{$infix}-n#{$size}, + .mx#{$infix}-n#{$size} { + margin-right: -$length !important; + } + .mb#{$infix}-n#{$size}, + .my#{$infix}-n#{$size} { + margin-bottom: -$length !important; + } + .ml#{$infix}-n#{$size}, + .mx#{$infix}-n#{$size} { + margin-left: -$length !important; + } + } + } + + // Some special margin utils + .m#{$infix}-auto { margin: auto !important; } + .mt#{$infix}-auto, + .my#{$infix}-auto { + margin-top: auto !important; + } + .mr#{$infix}-auto, + .mx#{$infix}-auto { + margin-right: auto !important; + } + .mb#{$infix}-auto, + .my#{$infix}-auto { + margin-bottom: auto !important; + } + .ml#{$infix}-auto, + .mx#{$infix}-auto { + margin-left: auto !important; + } + } +} diff --git a/src/theme/components/bootstrap/utilities/_text.scss b/src/theme/components/bootstrap/utilities/_text.scss new file mode 100644 index 000000000..79920db8e --- /dev/null +++ b/src/theme/components/bootstrap/utilities/_text.scss @@ -0,0 +1,64 @@ +// stylelint-disable declaration-no-important + +// +// Text +// + +.text-monospace { font-family: var(--mdl-typography-monospace-font) !important; } + +// Alignment + +.text-justify { text-align: justify !important; } +.text-wrap { white-space: normal !important; } +.text-nowrap { white-space: nowrap !important; } +.text-truncate { @include global.ellipsis(); } + +// Responsive alignment + +.text-left { text-align: left !important; } +.text-right { text-align: right !important; } +.text-center { text-align: center !important; } + +// Transformation + +.text-lowercase { text-transform: lowercase !important; } +.text-uppercase { text-transform: uppercase !important; } +.text-capitalize { text-transform: capitalize !important; } + +// Weight and italics + +.font-weight-light { font-weight: 300 !important; } +.font-weight-lighter { font-weight: lighter !important; } +.font-weight-normal { font-weight: 400 !important; } +.font-weight-bold { font-weight: 700 !important; } +.font-weight-bolder { font-weight: bolder !important; } +.font-italic { font-style: italic !important; } + +// Contextual colors + +.text-white { color: var(--white) !important; } + +@each $color-name, $unused in global.$colors { + .text-#{$color-name} { + color: var(--#{$color-name}); + } +} + +.text-body { color: var(--ion-text-color) !important; } +.text-muted { color: var(--subdued-text-color) !important; } + +.text-black-50 { color: rgb(0 0 0 / 50%) !important; } +.text-white-50 { color: rgb(255 255 255 / 50%) !important; } + +// Misc + +.text-decoration-none { text-decoration: none !important; } + +.text-break { + word-break: break-word !important; // Deprecated, but avoids issues with flex containers + word-wrap: break-word !important; // Used instead of `overflow-wrap` for IE & Edge Legacy +} + +// Reset + +.text-reset { color: inherit !important; } diff --git a/src/theme/components/bootstrap/utilities/_visibility.scss b/src/theme/components/bootstrap/utilities/_visibility.scss new file mode 100644 index 000000000..7756c3bfa --- /dev/null +++ b/src/theme/components/bootstrap/utilities/_visibility.scss @@ -0,0 +1,13 @@ +// stylelint-disable declaration-no-important + +// +// Visibility utilities +// + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} diff --git a/src/theme/components/bootstrap/vendor/_rfs.scss b/src/theme/components/bootstrap/vendor/_rfs.scss new file mode 100644 index 000000000..e453f441e --- /dev/null +++ b/src/theme/components/bootstrap/vendor/_rfs.scss @@ -0,0 +1,228 @@ +// stylelint-disable property-blacklist, scss/dollar-variable-default + +// SCSS RFS mixin +// +// Automated responsive font sizes +// +// Licensed under MIT (https://github.com/twbs/rfs/blob/v8.x/LICENSE) + +// Configuration + +// Base font size +$rfs-base-font-size: 1.25rem !default; +$rfs-font-size-unit: rem !default; + +@if $rfs-font-size-unit != rem and $rfs-font-size-unit != px { + @error "`#{$rfs-font-size-unit}` is not a valid unit for $rfs-font-size-unit. Use `px` or `rem`."; +} + +// Breakpoint at where font-size starts decreasing if screen width is smaller +$rfs-breakpoint: 1200px !default; +$rfs-breakpoint-unit: px !default; + +@if $rfs-breakpoint-unit != px and $rfs-breakpoint-unit != em and $rfs-breakpoint-unit != rem { + @error "`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`."; +} + +// Resize font size based on screen height and width +$rfs-two-dimensional: false !default; + +// Factor of decrease +$rfs-factor: 10 !default; + +@if type-of($rfs-factor) != "number" or $rfs-factor <= 1 { + @error "`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater than 1."; +} + +// Generate enable or disable classes. Possibilities: false, "enable" or "disable" +$rfs-class: false !default; + +// 1 rem = $rfs-rem-value px +$rfs-rem-value: 16 !default; + +// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14 +$rfs-safari-iframe-resize-bug-fix: false !default; + +// Disable RFS by setting $enable-responsive-font-sizes to false +$enable-responsive-font-sizes: true !default; + +// Cache $rfs-base-font-size unit +$rfs-base-font-size-unit: unit($rfs-base-font-size); + +@function divide($dividend, $divisor, $precision: 10) { + $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1); + $dividend: abs($dividend); + $divisor: abs($divisor); + @if $dividend == 0 { + @return 0; + } + @if $divisor == 0 { + @error "Cannot divide by 0"; + } + $remainder: $dividend; + $result: 0; + $factor: 10; + @while ($remainder > 0 and $precision >= 0) { + $quotient: 0; + @while ($remainder >= $divisor) { + $remainder: $remainder - $divisor; + $quotient: $quotient + 1; + } + $result: $result * 10 + $quotient; + $factor: $factor * .1; + $remainder: $remainder * 10; + $precision: $precision - 1; + @if ($precision < 0 and $remainder >= $divisor * 5) { + $result: $result + 1; + } + } + $result: $result * $factor * $sign; + $dividend-unit: unit($dividend); + $divisor-unit: unit($divisor); + $unit-map: ( + "px": 1px, + "rem": 1rem, + "em": 1em, + "%": 1% + ); + @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) { + $result: $result * map-get($unit-map, $dividend-unit); + } + @return $result; +} + +// Remove px-unit from $rfs-base-font-size for calculations +@if $rfs-base-font-size-unit == "px" { + $rfs-base-font-size: divide($rfs-base-font-size, $rfs-base-font-size * 0 + 1); +} +@else if $rfs-base-font-size-unit == "rem" { + $rfs-base-font-size: divide($rfs-base-font-size, divide($rfs-base-font-size * 0 + 1, $rfs-rem-value)); +} + +// Cache $rfs-breakpoint unit to prevent multiple calls +$rfs-breakpoint-unit-cache: unit($rfs-breakpoint); + +// Remove unit from $rfs-breakpoint for calculations +@if $rfs-breakpoint-unit-cache == "px" { + $rfs-breakpoint: divide($rfs-breakpoint, $rfs-breakpoint * 0 + 1); +} +@else if $rfs-breakpoint-unit-cache == "rem" or $rfs-breakpoint-unit-cache == "em" { + $rfs-breakpoint: divide($rfs-breakpoint, divide($rfs-breakpoint * 0 + 1, $rfs-rem-value)); +} + +// Internal mixin that adds disable classes to the selector if needed. +@mixin _rfs-disable-class { + @if $rfs-class == "disable" { + // Adding an extra class increases specificity, which prevents the media query to override the font size + &, + .disable-responsive-font-size &, + &.disable-responsive-font-size { + @content; + } + } + @else { + @content; + } +} + +// Internal mixin that adds enable classes to the selector if needed. +@mixin _rfs-enable-class { + @if $rfs-class == "enable" { + .enable-responsive-font-size &, + &.enable-responsive-font-size { + @content; + } + } + @else { + @content; + } +} + +// Internal mixin used to determine which media query needs to be used +@mixin _rfs-media-query($mq-value) { + @if $rfs-two-dimensional { + @media (max-width: #{$mq-value}), (max-height: #{$mq-value}) { + @content; + } + } + @else { + @media (max-width: #{$mq-value}) { + @content; + } + } +} + +// Responsive font size mixin +@mixin rfs($fs, $important: false) { + // Cache $fs unit + $fs-unit: if(type-of($fs) == "number", unit($fs), false); + + // Add !important suffix if needed + $rfs-suffix: if($important, " !important", ""); + + // If $fs isn't a number (like inherit) or $fs has a unit (not px or rem, like 1.5em) or $ is 0, just print the value + @if not $fs-unit or $fs-unit != "" and $fs-unit != "px" and $fs-unit != "rem" or $fs == 0 { + font-size: #{$fs}#{$rfs-suffix}; + } + @else { + // Remove unit from $fs for calculations + @if $fs-unit == "px" { + $fs: divide($fs, $fs * 0 + 1); + } + @else if $fs-unit == "rem" { + $fs: divide($fs, divide($fs * 0 + 1, $rfs-rem-value)); + } + + // Set default font size + $rfs-static: if($rfs-font-size-unit == rem, #{divide($fs, $rfs-rem-value)}rem, #{$fs}px); + + // Only add the media query if the font size is bigger than the minimum font size + @if $fs <= $rfs-base-font-size or not $enable-responsive-font-sizes { + font-size: #{$rfs-static}#{$rfs-suffix}; + } + @else { + // Calculate the minimum font size for $fs + $fs-min: $rfs-base-font-size + divide($fs - $rfs-base-font-size, $rfs-factor); + + // Calculate difference between $fs and the minimum font size + $fs-diff: $fs - $fs-min; + + // Base font-size formatting + $min-width: if($rfs-font-size-unit == rem, #{divide($fs-min, $rfs-rem-value)}rem, #{$fs-min}px); + + // Use `vmin` if two-dimensional is enabled + $variable-unit: if($rfs-two-dimensional, vmin, vw); + + // Calculate the variable width between 0 and $rfs-breakpoint + $variable-width: #{divide($fs-diff * 100, $rfs-breakpoint)}#{$variable-unit}; + + // Set the calculated font-size + $rfs-fluid: calc(#{$min-width} + #{$variable-width}) #{$rfs-suffix}; + + // Breakpoint formatting + $mq-value: if($rfs-breakpoint-unit == px, #{$rfs-breakpoint}px, #{divide($rfs-breakpoint, $rfs-rem-value)}#{$rfs-breakpoint-unit}); + + @include _rfs-disable-class { + font-size: #{$rfs-static}#{$rfs-suffix}; + } + + @include _rfs-media-query($mq-value) { + @include _rfs-enable-class { + font-size: $rfs-fluid; + } + + // Include safari iframe resize fix if needed + min-width: if($rfs-safari-iframe-resize-bug-fix, (0 * 1vw), null); + } + } + } +} + +// The font-size & responsive-font-size mixins use RFS to rescale the font size +@mixin font-size($fs, $important: false) { + @include rfs($fs, $important); +} + +@mixin responsive-font-size($fs, $important: false) { + @include rfs($fs, $important); +} diff --git a/src/theme/components/format-text.scss b/src/theme/components/format-text.scss index 6c315f262..944b205b5 100644 --- a/src/theme/components/format-text.scss +++ b/src/theme/components/format-text.scss @@ -364,343 +364,10 @@ core-rich-text-editor .core-rte-editor { flex-shrink: 0; } - // Atto styles - // ------------------------- - .atto_image_preview { - width: 100%; - height: 100%; - margin-left: auto; - margin-right: auto; - } - - .atto_image_preview_box { - max-height: 200px; - margin-bottom: 1em; - overflow: auto; - } - - .editor_atto_content img { - cursor: pointer; - } - - .atto_image_size { - display: inline-block; - } - - .atto_image_size input[type=checkbox] { - @include margin(null, 1em, null, 1em); - } - - .atto_image_size input[type=text] { - width: 3em; - } - - .atto_image_size label { - display: inline-block; - } - - .atto_image_button_text-top, - .atto_image_button_middle, - .atto_image_button_text-bottom, - .atto_image_button_left, - .atto_image_button_right { - vertical-align: middle; - max-width: 100%; - display: inline-block; - margin: 0 0.5em; - - &.img-responsive { - /* If the image is display: block then linking the image to URLs won't work. */ - /*display: inline-block;*/ - max-width: 100%; - } - } - - .atto_image_button_text-top { - vertical-align: text-top; - } - - .atto_image_button_middle { - vertical-align: middle; - } - - .atto_image_button_text-bottom { - vertical-align: text-bottom; - } - - .atto_image_button_left { - @include float(start); - @include margin(0, 0.5em, 0, 0); - } - - .atto_image_button_right { - @include float(end); - @include margin(0, 0, 0, 0.5em); - } - - // Bootstrap 4 Styles - // ------------------------- - - // _reboot.scss - fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; - } - - // _media.scss - .media { - display: flex; - align-items: flex-start; - } - - .media-body { - flex: 1; - } - - // _alert.scss - .alert { - position: relative; - padding: .75rem 1.25rem; - margin-bottom: 1rem; - border: 0 solid transparent; - border-radius: .5rem; - } - - // Headings for larger alerts - .alert-heading { - // Specified to prevent conflicts of changing headings-color - color: inherit; - } - - // Provide class for links that match alerts - .alert-link { - font-weight: 400; - } - - @each $color-name, $unused in global.$colors { - .alert-#{$color-name} { - --color-base: var(--#{$color-name}); - color: var(--#{$color-name}-shade); - border-color: var(--color-base); - background-color: var(--#{$color-name}-tint); - - .alert-link, a { - color: var(--#{$color-name}-shade); - } - } - .alert-#{$color-name} p { - color: var(--color-base); - } - } - - // _forms.scss - .form-check { - position: relative; - display: block; - padding-left: 1.25rem; - } - - .form-check-input { - position: absolute; - margin-top: .3rem; - margin-left: -1.25rem; - - &[disabled] ~ .form-check-label, - &:disabled ~ .form-check-label { - color: global.$gray-600; - } - - } - - .form-check-label { - margin-bottom: 0; - } - - .form-check-inline { - display: inline-flex; - align-items: center; - padding-left: 0; - margin-right: .75rem; - - .form-check-input { - position: static; - margin-top: 0; - margin-right: .3125rem; - margin-left: 0; - } - - } - - // utilities/_align.scss - .align-baseline { vertical-align: baseline !important; } // Browser default - .align-top { vertical-align: top !important; } - .align-middle { vertical-align: middle !important; } - .align-bottom { vertical-align: bottom !important; } - .align-text-bottom { vertical-align: text-bottom !important; } - .align-text-top { vertical-align: text-top !important; } - - // utilities/_border.scss - .border { border: 1px solid var(--gray-500) !important; } - .border-top { border-top: 1px solid var(--gray-500) !important; } - .border-right { border-right: 1px solid var(--gray-500) !important; } - .border-bottom { border-bottom: 1px solid var(--gray-500) !important; } - .border-left { border-left: 1px solid var(--gray-500) !important; } - - .border-0 { border: 0 !important; } - .border-top-0 { border-top: 0 !important; } - .border-right-0 { border-right: 0 !important; } - .border-bottom-0 { border-bottom: 0 !important; } - .border-left-0 { border-left: 0 !important; } - - @each $color-name, $unused in global.$colors { - .border-#{$color-name} { - border-color: var(--#{$color-name}) !important; - } - } - - .border-white { - border-color: var(--white) !important; - } - - // utilities/_flex.scss - .flex-row { flex-direction: row !important; } - .flex-column { flex-direction: column !important; } - .flex-row-reverse { flex-direction: row-reverse !important; } - .flex-column-reverse { flex-direction: column-reverse !important; } - - .flex-wrap { flex-wrap: wrap !important; } - .flex-nowrap { flex-wrap: nowrap !important; } - .flex-wrap-reverse { flex-wrap: wrap-reverse !important; } - .flex-fill { flex: 1 1 auto !important; } - .flex-grow-0 { flex-grow: 0 !important; } - .flex-grow-1 { flex-grow: 1 !important; } - .flex-shrink-0 { flex-shrink: 0 !important; } - .flex-shrink-1 { flex-shrink: 1 !important; } - - .justify-content-start { justify-content: flex-start !important; } - .justify-content-end { justify-content: flex-end !important; } - .justify-content-center { justify-content: center !important; } - .justify-content-between { justify-content: space-between !important; } - .justify-content-around { justify-content: space-around !important; } - - .align-items-start { align-items: flex-start !important; } - .align-items-end { align-items: flex-end !important; } - .align-items-center { align-items: center !important; } - .align-items-baseline { align-items: baseline !important; } - .align-items-stretch { align-items: stretch !important; } - - .align-content-start { align-content: flex-start !important; } - .align-content-end { align-content: flex-end !important; } - .align-content-center { align-content: center !important; } - .align-content-between { align-content: space-between !important; } - .align-content-around { align-content: space-around !important; } - .align-content-stretch { align-content: stretch !important; } - - .align-self-auto { align-self: auto !important; } - .align-self-start { align-self: flex-start !important; } - .align-self-end { align-self: flex-end !important; } - .align-self-center { align-self: center !important; } - .align-self-baseline { align-self: baseline !important; } - .align-self-stretch { align-self: stretch !important; } - - // utilities/_visibility.scss - .visible { - visibility: visible !important; - } - - .invisible { - visibility: hidden !important; - } - - // utilities/_text.scss - .text-monospace { font-family: var(--mdl-typography-monospace-font) !important; } - .text-justify { text-align: justify !important; } - .text-wrap { white-space: normal !important; } - .text-nowrap { white-space: nowrap !important; } - .text-truncate { - @include ellipsis(); - } - .text-left { text-align: left !important; } - .text-right { text-align: right !important; } - .text-center { text-align: center !important; } - .text-lowercase { text-transform: lowercase !important; } - .text-uppercase { text-transform: uppercase !important; } - .text-capitalize { text-transform: capitalize !important; } - .font-weight-light { font-weight: 300 !important; } - .font-weight-lighter { font-weight: lighter !important; } - .font-weight-normal { font-weight: 400 !important; } - .font-weight-bold { font-weight: 700 !important; } - .font-weight-bolder { font-weight: bolder !important; } - .font-italic { font-style: italic !important; } - .text-white { color: var(--white) !important; } - - @each $color-name, $unused in global.$colors { - .text-#{$color-name} { - color: var(--#{$color-name}); - } - } - - .text-body { color: var(--ion-text-color) !important; } - .text-muted { color: var(--subdued-text-color) !important; } - .text-black-50 { color: rgb(0 0 0 / 50%) !important; } - .text-white-50 { color: rgb(255 255 255 / 50%) !important; } - .text-decoration-none { text-decoration: none !important; } - .text-break { - word-break: break-word !important; // Deprecated, but avoids issues with flex containers - word-wrap: break-word !important; // Used instead of `overflow-wrap` for IE & Edge Legacy - } - .text-reset { color: inherit !important; } - - .label { - display: inline-block; - padding: .25em .4em; - font-size: 75%; - font-weight: 700; - line-height: 1.1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; - color: var(--white); - background-color: var(--medium); - } - - .label-important { - color: var(--danger-contrast); - background-color: var(--danger); - } - - @each $color-name, $unused in global.$colors { - .label-#{$color-name} { - color: var(--#{$color-name}-contrast); - background-color: var(--#{$color-name}); - } - } - - .btn-link { - background: none; - } - - button, .btn { - margin: 4px 8px; - padding-left: 12px; - padding-right: 12px; - border-radius: var(--core-input-radius); - a { - color: inherit; - } - } - - @each $color-name, $unused in global.$colors { - .btn.btn-#{$color-name} { - --color-base: var(--#{$color-name}); - color: var(--#{$color-name}-shade); - border-color: var(--color-base); - background-color: var(--#{$color-name}-tint); - } - } + @import "theme/components/atto.scss"; + // @TODO on 4.5 Remove the comment below. + // @import "theme/components/moodle.scss"; + @import "theme/components/bootstrap/bootstrap.scss"; } // h1 is too big and ugly, reduce size when loading. diff --git a/src/theme/components/moodle.scss b/src/theme/components/moodle.scss new file mode 100644 index 000000000..187c7dc37 --- /dev/null +++ b/src/theme/components/moodle.scss @@ -0,0 +1,3 @@ +.d-flex { + display: flex !important; +} diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index c7f4cf3e9..93ce2a0bc 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -63,7 +63,6 @@ html[dir=rtl] { flex-direction: row; } - .flex-column { display: flex; flex-direction: column; @@ -82,7 +81,6 @@ html[dir=rtl] { .font-lg { font-size: 1.7rem; } .font-sm { font-size: 1.2rem; } - @each $color-name, $unused in $colors { .text-#{$color-name}, p.text-#{$color-name} { diff --git a/src/theme/theme.scss b/src/theme/theme.scss index 4596a0cf2..31a41afe7 100644 --- a/src/theme/theme.scss +++ b/src/theme/theme.scss @@ -61,7 +61,7 @@ html { } /* Some styles from 3rd party libraries. */ -@import "bootstrap.scss"; +@import "components/bootstrap/utilities/screenreaders.scss"; /* Core CSS required for Ionic components to work properly */ @import "@ionic/angular/css/core.css";