diff --git a/src/app/common/flyout/flyout.component.scss b/src/app/common/flyout/flyout.component.scss
index be7f65da..5053cd76 100644
--- a/src/app/common/flyout/flyout.component.scss
+++ b/src/app/common/flyout/flyout.component.scss
@@ -1,5 +1,4 @@
@import '../../../styles/shared';
-@import '../../../styles/bootstrap/shared';
$flyout-border-top: 8px !default;
$flyout-box-shadow: 0 0.25rem 0.5rem rgba(var(--bs-body-bg-rgb), 0.2) !default;
diff --git a/src/app/common/system-alert/system-alert.component.scss b/src/app/common/system-alert/system-alert.component.scss
index cf0b656d..742f79e1 100644
--- a/src/app/common/system-alert/system-alert.component.scss
+++ b/src/app/common/system-alert/system-alert.component.scss
@@ -1,6 +1,5 @@
@use 'sass:map';
@import '../../../styles/shared';
-@import '../../../styles/bootstrap/shared';
$system-alert-icons: () !default;
$system-alert-icons: map.merge(
diff --git a/src/app/core/admin/messages/manage-message/manage-message.component.html b/src/app/core/admin/messages/manage-message/manage-message.component.html
index db11a7b8..ab5bfbce 100644
--- a/src/app/core/admin/messages/manage-message/manage-message.component.html
+++ b/src/app/core/admin/messages/manage-message/manage-message.component.html
@@ -21,6 +21,7 @@
{{ mode() | titlecase }} Message
name="type"
bindLabel="display"
bindValue="value"
+ required
style="width: 350px"
[(ngModel)]="message().type"
[items]="typeOptions"
diff --git a/src/app/core/feedback/feedback-flyout/feedback-flyout.component.scss b/src/app/core/feedback/feedback-flyout/feedback-flyout.component.scss
index 0305bdd4..384f7d68 100644
--- a/src/app/core/feedback/feedback-flyout/feedback-flyout.component.scss
+++ b/src/app/core/feedback/feedback-flyout/feedback-flyout.component.scss
@@ -1,5 +1,4 @@
@import '../../../../styles/shared';
-@import '../../../../styles/bootstrap/shared';
.feedback-form {
width: 550px;
diff --git a/src/app/core/messages/recent-messages/recent-messages.component.scss b/src/app/core/messages/recent-messages/recent-messages.component.scss
index 42f67ad3..8a61dad9 100644
--- a/src/app/core/messages/recent-messages/recent-messages.component.scss
+++ b/src/app/core/messages/recent-messages/recent-messages.component.scss
@@ -1,5 +1,4 @@
@use 'sass:map';
-@import '../../../../styles/bootstrap/shared';
@import '../../../../styles/shared';
@import '../shared';
diff --git a/src/styles/_bootstrap.scss b/src/styles/_bootstrap.scss
index c5287737..6b57f7b0 100644
--- a/src/styles/_bootstrap.scss
+++ b/src/styles/_bootstrap.scss
@@ -1,18 +1,60 @@
-@import 'bootstrap/shared';
-
-@import '../../node_modules/bootstrap/scss/bootstrap';
-
/**
* _bootstrap.scss
-
* This file is globally included in the Angular application.
* All bootstrap theme styles should be included in this file.
*/
+//@import '../../node_modules/bootstrap/scss/bootstrap';
+
+@import "bootstrap/shared";
+
+// Layout & components
+/**
+ * Individual components can be removed below to help reduce
+ * bundle size if not being used.
+ */
+@import "../../node_modules/bootstrap/scss/root";
+@import "../../node_modules/bootstrap/scss/reboot";
+@import "../../node_modules/bootstrap/scss/type";
+@import "../../node_modules/bootstrap/scss/images";
+@import "../../node_modules/bootstrap/scss/containers";
+@import "../../node_modules/bootstrap/scss/grid";
+@import "../../node_modules/bootstrap/scss/tables";
+@import "../../node_modules/bootstrap/scss/forms";
+@import "../../node_modules/bootstrap/scss/buttons";
+@import "../../node_modules/bootstrap/scss/transitions";
+@import "../../node_modules/bootstrap/scss/dropdown";
+@import "../../node_modules/bootstrap/scss/button-group";
+@import "../../node_modules/bootstrap/scss/nav";
+@import "../../node_modules/bootstrap/scss/navbar";
+@import "../../node_modules/bootstrap/scss/card";
+@import "../../node_modules/bootstrap/scss/accordion";
+@import "../../node_modules/bootstrap/scss/breadcrumb";
+@import "../../node_modules/bootstrap/scss/pagination";
+@import "../../node_modules/bootstrap/scss/badge";
+@import "../../node_modules/bootstrap/scss/alert";
+@import "../../node_modules/bootstrap/scss/progress";
+@import "../../node_modules/bootstrap/scss/list-group";
+@import "../../node_modules/bootstrap/scss/close";
+@import "../../node_modules/bootstrap/scss/toasts";
+@import "../../node_modules/bootstrap/scss/modal";
+@import "../../node_modules/bootstrap/scss/tooltip";
+@import "../../node_modules/bootstrap/scss/popover";
+@import "../../node_modules/bootstrap/scss/carousel";
+@import "../../node_modules/bootstrap/scss/spinners";
+@import "../../node_modules/bootstrap/scss/offcanvas";
+@import "../../node_modules/bootstrap/scss/placeholders";
+
+// Helpers
+@import "../../node_modules/bootstrap/scss/helpers";
+
+// Utilities
+@import "../../node_modules/bootstrap/scss/utilities/api";
+
+// Additional styles
@import 'bootstrap/buttons';
@import 'bootstrap/cards';
@import 'bootstrap/dropdowns';
@import 'bootstrap/forms';
@import 'bootstrap/grid';
-@import 'bootstrap/nav';
@import 'bootstrap/tables';
diff --git a/src/styles/_ng-select.scss b/src/styles/_ng-select.scss
index 021c7f58..01e2f9f5 100644
--- a/src/styles/_ng-select.scss
+++ b/src/styles/_ng-select.scss
@@ -1,6 +1,5 @@
@use 'sass:color';
@import 'shared';
-@import 'bootstrap/shared';
@import '../../node_modules/@ng-select/ng-select/scss/mixins'; /* stylelint-disable-line no-invalid-position-at-import-rule */
@@ -35,6 +34,41 @@ $ng-select-dropdown-option-bg: $ng-select-dropdown-bg !default;
$ng-select-dropdown-option-text: var(--bs-body-color) !default;
$ng-select-dropdown-option-disabled: color.adjust($ng-select-primary-text-old, $lightness: 60%) !default;
+@mixin ng-select-form-validation-state(
+ $state,
+ $color,
+ $icon,
+ $tooltip-color: color-contrast($color),
+ $tooltip-bg-color: rgba($color, $form-feedback-tooltip-opacity),
+ $focus-box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($color, $input-btn-focus-color-opacity),
+ $border-color: $color
+) {
+ @include form-validation-state-selector($state) {
+ .ng-select-container {
+ border-color: $border-color;
+
+ @if $enable-validation-icons {
+ padding-right: $input-height-inner;
+ 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;
+ }
+ }
+ &.ng-select-focused {
+ &:not(.ng-select-opened) > .ng-select-container {
+ border-color: $border-color;
+ @if $enable-shadows {
+ @include box-shadow($input-box-shadow, $focus-box-shadow);
+ } @else {
+ // Avoid using mixin so we can pass custom focus shadow properly
+ box-shadow: $focus-box-shadow;
+ }
+ }
+ }
+ }
+}
+
.ng-select {
&.ng-select-opened {
> .ng-select-container {
@@ -253,6 +287,10 @@ $ng-select-dropdown-option-disabled: color.adjust($ng-select-primary-text-old, $
border-width: 5px 5px 2.5px;
}
}
+
+ @each $state, $data in $form-validation-states {
+ @include ng-select-form-validation-state($state, $data...);
+ }
}
.ng-dropdown-panel {
diff --git a/src/styles/bootstrap/_buttons.scss b/src/styles/bootstrap/_buttons.scss
index 9ba77ebb..4eac5a00 100644
--- a/src/styles/bootstrap/_buttons.scss
+++ b/src/styles/bootstrap/_buttons.scss
@@ -1,4 +1,5 @@
@use 'sass:math';
+@import 'shared';
// Submit Button
$btn-submit-style: spinner !default;
diff --git a/src/styles/bootstrap/_forms.scss b/src/styles/bootstrap/_forms.scss
index 813c5a4d..5bddb70c 100644
--- a/src/styles/bootstrap/_forms.scss
+++ b/src/styles/bootstrap/_forms.scss
@@ -77,3 +77,16 @@ $form-inline-edit-label-col-width: 30% !default;
}
}
}
+
+@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;
+ }
+ }
+}
diff --git a/src/styles/bootstrap/_mixins_override.scss b/src/styles/bootstrap/_mixins_override.scss
new file mode 100644
index 00000000..050bd622
--- /dev/null
+++ b/src/styles/bootstrap/_mixins_override.scss
@@ -0,0 +1,21 @@
+// Mixins defined here will override mixins of the same name provided by bootstrap
+
+@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;
+ }
+ }
+ // Add bootstrap validation styling based on ng-invalid
+ @if ($state == "invalid") {
+ #{if(&, "&", "")}.ng-#{$state}.ng-dirty,
+ #{if(&, "&", "")}.ng-#{$state}.ng-touched {
+ @content;
+ }
+ }
+}
diff --git a/src/styles/bootstrap/_nav.scss b/src/styles/bootstrap/_nav.scss
deleted file mode 100644
index 8b137891..00000000
--- a/src/styles/bootstrap/_nav.scss
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/styles/bootstrap/_shared.scss b/src/styles/bootstrap/_shared.scss
index fd250571..7053ec81 100644
--- a/src/styles/bootstrap/_shared.scss
+++ b/src/styles/bootstrap/_shared.scss
@@ -1,7 +1,13 @@
-// Imports non-rendering bootstrap SASS (i.e. variables, functions, mixins) for easy import into other files.
+// Variable overrides must be imported before the variables are defined
+@import 'variables_overrides';
-@import 'variables';
+// Configuration
+@import "../../../node_modules/bootstrap/scss/functions";
+@import "../../../node_modules/bootstrap/scss/variables";
+@import "../../../node_modules/bootstrap/scss/variables-dark";
+@import "../../../node_modules/bootstrap/scss/maps";
+@import "../../../node_modules/bootstrap/scss/mixins";
+@import "../../../node_modules/bootstrap/scss/utilities";
-@import '../../../node_modules/bootstrap/scss/functions';
-@import '../../../node_modules/bootstrap/scss/variables';
-@import '../../../node_modules/bootstrap/scss/mixins';
+// Mixin overrides must be imported before the mixins are used.
+@import "mixins_override";
diff --git a/src/styles/bootstrap/_variables.scss b/src/styles/bootstrap/_variables_overrides.scss
similarity index 100%
rename from src/styles/bootstrap/_variables.scss
rename to src/styles/bootstrap/_variables_overrides.scss