@saooti/octopus-sdk 33.1.3 → 33.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -0
  2. package/package.json +1 -3
  3. package/src/App.vue +1 -1
  4. package/src/assets/_utilities.scss +522 -0
  5. package/src/assets/bootstrap.scss +181 -0
  6. package/src/assets/form.scss +0 -20
  7. package/src/assets/general.scss +1 -0
  8. package/src/assets/modal.scss +49 -8
  9. package/src/assets/octopus-library.scss +2 -3
  10. package/src/components/display/categories/CategoryChooser.vue +1 -1
  11. package/src/components/display/categories/CategoryFilter.vue +60 -46
  12. package/src/components/display/categories/CategoryList.vue +22 -19
  13. package/src/components/display/comments/AddCommentModal.vue +60 -67
  14. package/src/components/display/comments/CommentBasicView.vue +2 -3
  15. package/src/components/display/emission/EmissionItem.vue +2 -1
  16. package/src/components/display/emission/EmissionPlayerItem.vue +0 -11
  17. package/src/components/display/participant/ParticipantItem.vue +2 -1
  18. package/src/components/display/playlist/PlaylistItem.vue +2 -1
  19. package/src/components/display/podcasts/PodcastInlineList.vue +1 -1
  20. package/src/components/display/podcasts/PodcastInlineListClassic.vue +1 -1
  21. package/src/components/display/podcasts/PodcastInlineListTemplate.vue +6 -2
  22. package/src/components/display/podcasts/PodcastPlayBar.vue +9 -35
  23. package/src/components/display/podcasts/TagList.vue +0 -2
  24. package/src/components/display/rubriques/RubriqueList.vue +25 -20
  25. package/src/components/display/sharing/PlayerParameters.vue +76 -99
  26. package/src/components/display/sharing/ShareButtons.vue +3 -5
  27. package/src/components/display/sharing/ShareButtonsIntern.vue +1 -1
  28. package/src/components/display/sharing/ShareDistribution.vue +11 -10
  29. package/src/components/display/sharing/SubscribeButtons.vue +1 -1
  30. package/src/components/form/ClassicLoading.vue +5 -1
  31. package/src/components/misc/Accordion.vue +48 -0
  32. package/src/components/misc/ErrorMessage.vue +2 -1
  33. package/src/components/misc/HomeDropdown.vue +66 -63
  34. package/src/components/misc/Nav.vue +92 -0
  35. package/src/components/misc/Popover.vue +137 -98
  36. package/src/components/misc/ProgressBar.vue +96 -0
  37. package/src/components/misc/Spinner.vue +26 -0
  38. package/src/components/misc/modal/ClassicModal.vue +141 -0
  39. package/src/components/misc/modal/ClipboardModal.vue +25 -40
  40. package/src/components/misc/modal/MessageModal.vue +45 -60
  41. package/src/components/misc/modal/NewsletterModal.vue +84 -92
  42. package/src/components/misc/modal/QrCodeModal.vue +19 -36
  43. package/src/components/misc/modal/ShareModalPlayer.vue +69 -133
  44. package/src/components/misc/player/Player.vue +0 -6
  45. package/src/components/misc/player/PlayerCompact.vue +4 -3
  46. package/src/components/misc/player/PlayerLarge.vue +12 -8
  47. package/src/components/misc/player/PlayerProgressBar.vue +10 -48
  48. package/src/components/pages/Podcast.vue +1 -1
  49. package/src/assets/bootstrap-diff.scss +0 -195
@@ -9,86 +9,89 @@
9
9
  >
10
10
  {{ $t('My space') }}
11
11
  </button>
12
- <div
13
- class="dropdown btn-group"
12
+ <button
13
+ id="home-dropdown"
14
+ class="btn m-1 admin-button saooti-user"
15
+ :title="$t('User menu')"
16
+ />
17
+ <Popover
18
+ target="home-dropdown"
19
+ :only-click="true"
20
+ :is-fixed="true"
21
+ :left-pos="true"
14
22
  >
15
- <button
16
- class="btn dropdown-toggle m-1 admin-button dropdown-toggle-no-caret saooti-user"
17
- data-bs-toggle="dropdown"
18
- aria-expanded="false"
19
- :title="$t('User menu')"
20
- />
21
- <div class="dropdown-menu dropdown-menu-right px-4">
22
- <template v-if="!authenticated">
23
- <a
24
- class="dropdown-item"
25
- href="/sso/login"
26
- >
27
- {{ $t('Login') }}
28
- </a>
23
+ <template v-if="!authenticated">
24
+ <a
25
+ class="octopus-dropdown-item"
26
+ href="/sso/login"
27
+ >
28
+ {{ $t('Login') }}
29
+ </a>
30
+ <router-link
31
+ v-if="!isPodcastmaker"
32
+ class="octopus-dropdown-item"
33
+ to="/main/pub/create"
34
+ >
35
+ {{ $t('Create an account') }}
36
+ </router-link>
37
+ </template>
38
+ <template v-else>
39
+ <template
40
+ v-for="routerBack in routerBackoffice"
41
+ :key="routerBack.path"
42
+ >
29
43
  <router-link
30
- v-if="!isPodcastmaker"
31
- class="dropdown-item"
32
- to="/main/pub/create"
44
+ v-if="!isPodcastmaker && routerBack.condition"
45
+ :class="routerBack.class"
46
+ :to="routerBack.path"
33
47
  >
34
- {{ $t('Create an account') }}
48
+ {{ routerBack.title }}
35
49
  </router-link>
36
50
  </template>
37
- <template v-else>
51
+ <template v-if="!isEducation">
52
+ <hr>
38
53
  <template
39
- v-for="routerBack in routerBackoffice"
40
- :key="routerBack.path"
54
+ v-for="helpLink in helpLinks"
55
+ :key="helpLink.title"
41
56
  >
42
- <router-link
43
- v-if="!isPodcastmaker && routerBack.condition"
44
- :class="routerBack.class"
45
- :to="routerBack.path"
57
+ <a
58
+ :href="helpLink.href"
59
+ class="octopus-dropdown-item"
60
+ rel="noopener"
61
+ target="_blank"
46
62
  >
47
- {{ routerBack.title }}
48
- </router-link>
63
+ {{ helpLink.title }}
64
+ </a>
49
65
  </template>
50
- <template v-if="!isEducation">
51
- <hr class="dropdown-divider">
52
- <template
53
- v-for="helpLink in helpLinks"
54
- :key="helpLink.title"
55
- >
56
- <a
57
- :href="helpLink.href"
58
- class="dropdown-item"
59
- rel="noopener"
60
- target="_blank"
61
- >
62
- {{ helpLink.title }}
63
- </a>
64
- </template>
65
- </template>
66
- <hr class="dropdown-divider">
67
- <a
68
- class="dropdown-item"
69
- href="/sso/logout"
70
- >
71
- {{ $t('Logout') }}
72
- </a>
73
66
  </template>
74
- <router-link
75
- class="dropdown-item"
76
- to="/main/pub/contact"
67
+ <hr>
68
+ <a
69
+ class="octopus-dropdown-item"
70
+ href="/sso/logout"
77
71
  >
78
- {{ $t('Contact') }}
79
- </router-link>
80
- </div>
81
- </div>
72
+ {{ $t('Logout') }}
73
+ </a>
74
+ </template>
75
+ <router-link
76
+ class="octopus-dropdown-item"
77
+ to="/main/pub/contact"
78
+ >
79
+ {{ $t('Contact') }}
80
+ </router-link>
81
+ </Popover>
82
82
  </div>
83
83
  </template>
84
84
 
85
85
  <script lang="ts">
86
86
  import { state } from '../../store/paramStore';
87
-
87
+ import Popover from '../misc/Popover.vue';
88
88
  import { defineComponent } from 'vue'
89
89
  import { Organisation } from '@/store/class/general/organisation';
90
90
  export default defineComponent({
91
91
  name: 'HomeDropdown',
92
+ components:{
93
+ Popover
94
+ },
92
95
  props: {
93
96
  isEducation: { default: false, type: Boolean},
94
97
  },
@@ -104,9 +107,9 @@ export default defineComponent({
104
107
  routerBackoffice(){
105
108
  return [
106
109
  {title:this.$t('Upload'),class:"btn btn-primary w-100", path:'/main/priv/upload', condition: (state.generalParameters.isContribution as boolean)},
107
- {title:this.$t('My space'),class:"show-phone dropdown-item", path:'/main/priv/backoffice', condition: true},
108
- {title:this.$t('Edit my profile'),class:"dropdown-item", path:'/main/priv/edit/profile', condition: true},
109
- {title:this.$t('Edit my organisation'),class:"dropdown-item", path:'/main/priv/edit/organisation', condition: (state.generalParameters.isOrganisation as boolean) || 1<this.organisationsAvailable.length}];
110
+ {title:this.$t('My space'),class:"show-phone octopus-dropdown-item", path:'/main/priv/backoffice', condition: true},
111
+ {title:this.$t('Edit my profile'),class:"octopus-dropdown-item", path:'/main/priv/edit/profile', condition: true},
112
+ {title:this.$t('Edit my organisation'),class:"octopus-dropdown-item", path:'/main/priv/edit/organisation', condition: (state.generalParameters.isOrganisation as boolean) || 1<this.organisationsAvailable.length}];
110
113
  },
111
114
 
112
115
  isPodcastmaker(): boolean {
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <ul class="octopus-nav">
3
+ <li
4
+ v-for="index in tabNumber"
5
+ :key="index-1"
6
+ class="octopus-nav-item"
7
+ >
8
+ <div
9
+ class="octopus-nav-link"
10
+ :class="activeTab === (index-1)? 'active':''"
11
+ @click="activeTab = (index-1)"
12
+ >
13
+ <slot :name="(index-1)" />
14
+ </div>
15
+ </li>
16
+ </ul>
17
+ <div class="octopus-tab-content">
18
+ <div
19
+ v-for="index in tabNumber"
20
+ :key="index-1"
21
+ class="octopus-tab-pane"
22
+ :class="activeTab === (index-1)? 'active':''"
23
+ >
24
+ <slot :name="'tab'+(index-1)" />
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script lang="ts">
30
+ import { defineComponent } from 'vue'
31
+ export default defineComponent({
32
+ name: 'Nav',
33
+ props: {
34
+ tabNumber: { default: 0, type: Number},
35
+ },
36
+ data() {
37
+ return {
38
+ activeTab: 0 as number,
39
+ };
40
+ },
41
+ })
42
+ </script>
43
+
44
+ <style lang="scss">
45
+ @import '@scss/_variables.scss';
46
+ .octopus-app{
47
+ .octopus-nav{
48
+ display: flex;
49
+ flex-wrap: wrap;
50
+ padding-left: 0;
51
+ margin-bottom: 0;
52
+ list-style: none;
53
+ border-bottom: 0.05rem solid #ddd;
54
+ }
55
+ .octopus-nav-item{
56
+ border-right: solid 1px rgb(222,226,230);
57
+ border-left: solid 1px rgb(222,226,230);
58
+ border-top: solid 1px rgb(222,226,230);
59
+ border-top-left-radius: 0.25rem;
60
+ border-top-right-radius: 0.25rem;
61
+ cursor: pointer;
62
+ flex-grow: 1;
63
+ text-align: center;
64
+ }
65
+ .octopus-nav-link{
66
+ display: block;
67
+ padding: 0.5rem 1rem;
68
+ text-decoration: none;
69
+ transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;
70
+ border: 0.1rem solid transparent;
71
+ &:hover,&.active{
72
+ border-color: #dee2e6;
73
+ border-bottom-color: $octopus-primary-color;
74
+ color: $octopus-primary-color;
75
+ }
76
+ }
77
+ .octopus-tab-content{
78
+ border-right: solid 1px rgb(222,226,230);
79
+ border-left: solid 1px rgb(222,226,230);
80
+ border-bottom: solid 1px rgb(222,226,230);
81
+ background-color: #f8fafc;
82
+ }
83
+ .octopus-tab-pane{
84
+ display: none;
85
+ &.active{
86
+ display: flex;
87
+ justify-content: space-between;
88
+ padding: 0.5rem;
89
+ }
90
+ }
91
+ }
92
+ </style>
@@ -1,122 +1,161 @@
1
1
  <template>
2
- <div
3
- :id="id"
4
- ref="element"
5
- class="popover"
6
- role="tooltip"
7
- tabindex="-1"
2
+ <div
3
+ v-show="show && !disable"
4
+ :id="'popover'+target"
5
+ ref="popover"
6
+ tabindex="0"
7
+ class="octopus-popover"
8
+ :class="onlyClick?'octopus-dropdown':''"
9
+ :style="positionInlineStyle"
10
+ @blur="clearDataBlur"
8
11
  >
9
- <div ref="titleRef">
10
- <slot name="title">
11
- {{ title }}
12
- </slot>
12
+ <div
13
+ v-if="title"
14
+ class="bg-secondary p-2"
15
+ >
16
+ {{ title }}
13
17
  </div>
14
- <div ref="contentRef">
15
- <slot>
16
- {{ content }}
17
- </slot>
18
+ <div class="p-2">
19
+ <slot>{{ content }}</slot>
18
20
  </div>
19
21
  </div>
20
22
  </template>
21
23
 
22
24
  <script lang="ts">
23
- import {Popover} from 'bootstrap'
24
- import {defineComponent, onMounted, PropType, ref} from 'vue'
25
- import useEventListener from '../../helper/useEventListener'
25
+ import {defineComponent} from 'vue';
26
26
  export default defineComponent({
27
27
  name: 'Popover',
28
- props: {
28
+ props: {
29
29
  content: {type: String, default: ''},
30
- id: {type: String, default: ''},
31
- noninteractive: {type: Boolean, default: false},
32
- placement: {type: String as PropType<Popover.Options['placement']>, default: 'right'},
30
+ title: {type: String, default: ''},
33
31
  target: {type: String, required: true},
34
- title: {type: String, default: ''},
35
- show: {type: Boolean, default: false},
36
32
  disable: {type: Boolean, default: false},
33
+ onlyClick: {type: Boolean, default: false},
34
+ isFixed: {type: Boolean, default: false},
35
+ leftPos: {type: Boolean, default: false},
37
36
  },
38
- emits: ['show', 'shown', 'hide', 'hidden', 'inserted'],
39
- setup(props, {emit}) {
40
- const element = ref<HTMLElement>()
41
- const target = ref<HTMLElement>()
42
- const instance = ref<Popover>()
43
- const titleRef = ref<HTMLElement>()
44
- const contentRef = ref<HTMLElement>()
45
- function initPopover(){
46
- instance.value = new Popover(`#${props.target}`, {
47
- container: 'body',
48
- trigger: "hover focus",
49
- placement: props.placement,
50
- title: titleRef.value?.innerHTML || '',
51
- content: contentRef.value?.innerHTML || '',
52
- html: true,
53
- })
54
- if (document.getElementById(props.target)) {
55
- target.value = document.getElementById(props.target) as HTMLElement
56
- }
57
- element.value?.parentNode?.removeChild(element.value)
58
- if (props.show) {
59
- instance.value.show()
60
- }
61
- if (props.disable) {
62
- instance.value.disable()
63
- }
64
- }
65
- onMounted(()=>{
66
- initPopover();
67
- })
68
- useEventListener(target, 'show.bs.popover', () => emit('show'))
69
- useEventListener(target, 'shown.bs.popover', () => emit('shown'))
70
- useEventListener(target, 'hide.bs.popover', () => emit('hide'))
71
- useEventListener(target, 'hidden.bs.popover', () => emit('hidden'))
72
- useEventListener(target, 'inserted.bs.popover', () => emit('inserted'))
37
+ data () {
73
38
  return {
74
- element,
75
- titleRef,
76
- contentRef,
77
- instance,
78
- initPopover
39
+ show: false as boolean,
40
+ isClick: false as boolean,
41
+ posX: 0 as number,
42
+ posY: 0 as number,
43
+ targetElement: null as HTMLElement|null,
44
+ }
45
+ },
46
+ computed: {
47
+ popoverId(): string{
48
+ return 'popover'+this.target;
49
+ },
50
+ positionInlineStyle(): string {
51
+ return `left: ${this.posX}px; top: ${this.posY}px;`;
79
52
  }
80
53
  },
81
- watch:{
82
- disable(){
83
- if(!this.instance){ return; }
84
- if (this.disable) {
85
- this.instance.disable();
86
- } else {
87
- this.instance.enable();
88
- }
54
+ mounted () {
55
+ this.init();
56
+ },
57
+ unmounted(){
58
+ this.removeListeners();
59
+ },
60
+ methods: {
61
+ init () {
62
+ this.targetElement = document.getElementById(this.target);
63
+ if(this.targetElement){
64
+ if(!this.onlyClick){
65
+ this.targetElement.addEventListener("mouseenter", this.setPopoverData);
66
+ this.targetElement.addEventListener("mouseleave", this.clearData);
67
+ }
68
+ this.targetElement.addEventListener("click", this.setPopoverData);
69
+ this.targetElement.addEventListener("blur", this.clearDataBlur);
70
+ }
89
71
  },
90
- content(){
91
- if(!this.instance){ return; }
92
- this.$nextTick(() => {
93
- this.instance?.dispose();
94
- this.initPopover();
95
- });
72
+ removeListeners () {
73
+ if(this.targetElement){
74
+ if(!this.onlyClick){
75
+ this.targetElement.removeEventListener("mouseenter", this.setPopoverData);
76
+ this.targetElement.removeEventListener("mouseleave", this.clearData);
77
+ }
78
+ this.targetElement.removeEventListener("click", this.setPopoverData);
79
+ this.targetElement.addEventListener("blur", this.clearDataBlur);
80
+ }
96
81
  },
97
- title(){
98
- if(!this.instance){ return; }
99
- this.$nextTick(() => {
100
- this.instance?.dispose();
101
- this.initPopover();
102
- });
82
+ setPopoverData (e: MouseEvent|PointerEvent) {
83
+ if(e && e.target){
84
+ if("click"===e.type){
85
+ if(this.show && this.isClick){
86
+ this.isClick = false;
87
+ this.clearData();
88
+ return;
89
+ }
90
+ this.isClick = true;
91
+ }
92
+ this.show = true;
93
+ const rectElement = (e.target as HTMLElement).getBoundingClientRect();
94
+ (this.$refs.popover as HTMLElement).style.display = 'block';
95
+ this.posX = this.leftPos? rectElement.right - (this.$refs.popover as HTMLElement).clientWidth : rectElement.left;
96
+ this.posY = rectElement.bottom + (this.isFixed ? 0 : window.scrollY)+ 5;
97
+ }
98
+ },
99
+ clearDataBlur (e: FocusEvent) {
100
+ if(e.relatedTarget){
101
+ const myElement = e.relatedTarget as HTMLElement;
102
+ if(this.popoverId===myElement.id){return;}
103
+ const parent = this.$refs.popover as HTMLElement;
104
+ if (null!==parent && parent.contains(myElement)) {
105
+ if(null!==myElement.classList && myElement.classList.contains('octopus-dropdown-item')){
106
+ this.$nextTick(() => {
107
+ this.isClick = false;
108
+ this.clearData();
109
+ });
110
+ }
111
+ return;
112
+ }
113
+ }
114
+ this.isClick = false;
115
+ this.clearData();
116
+ },
117
+ clearData () {
118
+ if(this.isClick){
119
+ return;
120
+ }
121
+ this.show = false;
122
+ this.posX = 0;
123
+ this.posY = 0;
103
124
  }
104
- }
105
-
106
- })
125
+ },
126
+ });
107
127
  </script>
108
128
  <style lang="scss">
109
- .popover{
110
- max-height: 80vh;
111
- max-width: 50vw !important;
112
- display: flex !important;
113
- flex-direction: column;
114
- hr{
115
- width: 100px;
116
- }
117
- .popover-body{
118
- overflow: auto;
119
- height: 100%;
120
- }
129
+ .octopus-popover{
130
+ background: white;
131
+ border: 1px solid #ccc;
132
+ border-radius: 5px;
133
+ position: absolute;
134
+ z-index: 9999;
135
+ &.octopus-dropdown{
136
+ min-width: 200px;
137
+ padding: 0.5rem 1rem;
138
+ .octopus-dropdown-item{
139
+ display: block;
140
+ color: rgb(29, 29, 29);
141
+ width: 100%;
142
+ padding: 0.25rem 1rem;
143
+ font-weight: 400;
144
+ text-align: inherit;
145
+ text-decoration: none;
146
+ white-space: nowrap;
147
+ background-color: transparent;
148
+ border: 0;
149
+ &:hover{
150
+ background: rgb(243, 243, 243);
151
+ }
152
+ }
153
+ hr{
154
+ margin: 0.5rem 0;
155
+ overflow: hidden;
156
+ border-top: 1px solid #ccc;
157
+ opacity: 1;
158
+ }
159
+ }
121
160
  }
122
161
  </style>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <div
3
+ class="octopus-progress"
4
+ >
5
+ <div
6
+ v-if="secondaryProgress"
7
+ class="octopus-progress-bar bg-light"
8
+ role="progressbar"
9
+ aria-valuenow="0"
10
+ aria-valuemin="0"
11
+ aria-valuemax="100"
12
+ :style="'width: ' + secondaryProgress + '%'"
13
+ />
14
+ <div
15
+ class="octopus-progress-bar primary-bg"
16
+ role="progressbar"
17
+ aria-valuenow="0"
18
+ aria-valuemin="0"
19
+ aria-valuemax="100"
20
+ :style="'width: ' + mainProgress + '%'"
21
+ />
22
+ <div
23
+ v-if="alertBar"
24
+ class="octopus-progress-bar octopus-progress-bar-duration bg-danger"
25
+ :style="'left: ' + alertBar + '%'"
26
+ />
27
+ <div
28
+ v-if="isProgressCursor"
29
+ class="progress-bar-cursor"
30
+ :style="'left:' + mainProgress + '%'"
31
+ />
32
+ </div>
33
+ </template>
34
+
35
+ <script lang="ts">
36
+ import { defineComponent } from 'vue';
37
+ export default defineComponent({
38
+ name: 'ProgressBar',
39
+ props: {
40
+ alertBar: { default: undefined, type: Number},
41
+ mainProgress: { default: 0, type: Number},
42
+ secondaryProgress: { default: 0, type: Number},
43
+ isProgressCursor: { default: false, type: Boolean},
44
+ },
45
+ })
46
+ </script>
47
+
48
+ <style lang="scss">
49
+ @import '@scss/_variables.scss';
50
+ .octopus-app{
51
+ .octopus-progress{
52
+ display: flex;
53
+ overflow: hidden;
54
+ background-color:#e9ecef;
55
+ border-radius: 0.375rem;
56
+ position: relative;
57
+ cursor: pointer;
58
+
59
+ .octopus-progress-bar{
60
+ position: absolute;
61
+ display: flex;
62
+ flex-direction: column;
63
+ justify-content: center;
64
+ overflow: hidden;
65
+ color: white;
66
+ text-align: center;
67
+ white-space: nowrap;
68
+ background-color: $octopus-primary-color;
69
+ transition: width 0.6s ease;
70
+ }
71
+ &,.octopus-progress-bar{
72
+ height: 4px;
73
+ @media (max-width: 960px) {
74
+ height: 8px;
75
+ }
76
+ }
77
+ &.large,&.large .octopus-progress-bar{
78
+ height: 15px;
79
+ }
80
+ &.medium,&.medium .octopus-progress-bar{
81
+ height: 6px;
82
+ }
83
+ .octopus-progress-bar-duration {
84
+ width: 10px;
85
+ }
86
+ .octopus-progress-bar-cursor{
87
+ width: 10px;
88
+ height: 10px;
89
+ border-radius: 50%;
90
+ background: black;
91
+ align-self: center;
92
+ position: absolute;
93
+ }
94
+ }
95
+ }
96
+ </style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="octopus-spinner" />
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { defineComponent } from 'vue';
7
+ export default defineComponent({
8
+ name: "Spinner",
9
+ })
10
+ </script>
11
+ <style lang="scss">
12
+ .octopus-app .octopus-spinner{
13
+ display: inline-block;
14
+ width:2rem;
15
+ height:2rem;
16
+ border-radius: 50%;
17
+ animation: 0.75s linear infinite spinner-border;
18
+ border: 0.25rem solid currentcolor;
19
+ border-right-color: transparent;
20
+ flex-shrink: 0;
21
+
22
+ @keyframes spinner-border {
23
+ to { transform: rotate(360deg); }
24
+ }
25
+ }
26
+ </style>