@ditojs/admin 2.2.12 → 2.2.14

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.2.12",
3
+ "version": "2.2.14",
4
4
  "type": "module",
5
5
  "description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/admin",
@@ -33,7 +33,7 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.2.10",
36
+ "@ditojs/ui": "^2.2.14",
37
37
  "@ditojs/utils": "^2.2.0",
38
38
  "@kyvg/vue3-notification": "^2.9.0",
39
39
  "@lk77/vue3-color": "^3.0.6",
@@ -66,6 +66,7 @@
66
66
  "nanoid": "^4.0.2",
67
67
  "sortablejs": "^1.15.0",
68
68
  "tinycolor2": "^1.6.0",
69
+ "tippy.js": "^6.3.7",
69
70
  "type-fest": "^3.8.0",
70
71
  "vue": "^3.2.47",
71
72
  "vue-multiselect": "^3.0.0-beta.1",
@@ -82,7 +83,7 @@
82
83
  "vite": "^4.3.1"
83
84
  },
84
85
  "types": "types",
85
- "gitHead": "062d8d229de60f7c81677b927c5c66ce9f2d2337",
86
+ "gitHead": "71b48376ab32cc6ef6cd77bc565501ac264683d7",
86
87
  "scripts": {
87
88
  "build": "vite build",
88
89
  "watch": "yarn build --mode 'development' --watch",
@@ -6,7 +6,7 @@
6
6
  v-for="(buttonSchema, buttonDataPath) in buttonSchemas"
7
7
  )
8
8
  DitoContainer(
9
- v-if="shouldRender(buttonSchema)"
9
+ v-if="shouldRenderSchema(buttonSchema)"
10
10
  :key="buttonDataPath"
11
11
  :schema="buttonSchema"
12
12
  :dataPath="buttonDataPath"
@@ -6,21 +6,24 @@
6
6
  )
7
7
  DitoLabel(
8
8
  v-if="hasLabel"
9
+ :class="componentClass"
9
10
  :label="label"
10
11
  :dataPath="labelDataPath"
11
- :class="componentClass"
12
+ :info="info"
12
13
  )
13
14
  component.dito-component(
14
15
  :is="typeComponent"
16
+ :class="componentClass"
15
17
  :schema="schema"
16
18
  :dataPath="dataPath"
17
19
  :data="data"
18
20
  :meta="meta"
19
21
  :store="store"
22
+ :width="width"
23
+ :label="label"
20
24
  :single="single"
21
25
  :nested="nested"
22
26
  :disabled="componentDisabled"
23
- :class="componentClass"
24
27
  @errors="onErrors"
25
28
  )
26
29
  DitoErrors(:errors="errors")
@@ -84,7 +87,12 @@ export default DitoComponent.component('DitoContainer', {
84
87
  return this.nested ? this.dataPath : null
85
88
  },
86
89
 
87
- componentWidth: getSchemaAccessor('width', {
90
+ info: getSchemaAccessor('info', {
91
+ type: String,
92
+ default: null
93
+ }),
94
+
95
+ width: getSchemaAccessor('width', {
88
96
  type: [String, Number],
89
97
  default() {
90
98
  return this.typeComponent?.defaultWidth
@@ -99,7 +107,7 @@ export default DitoComponent.component('DitoContainer', {
99
107
  }
100
108
  }),
101
109
 
102
- componentWidthOperator: getSchemaAccessor('width', {
110
+ widthOperator: getSchemaAccessor('width', {
103
111
  type: String,
104
112
  get(width) {
105
113
  return isString(width)
@@ -143,7 +151,7 @@ export default DitoComponent.component('DitoContainer', {
143
151
  },
144
152
 
145
153
  componentBasis() {
146
- const width = this.componentWidth
154
+ const width = this.width
147
155
  // 'auto' = no fitting:
148
156
  const basis = [null, 'auto', 'fill'].includes(width)
149
157
  ? 'auto'
@@ -158,11 +166,11 @@ export default DitoComponent.component('DitoContainer', {
158
166
  containerStyle() {
159
167
  // Interpret '>50%' as '50%, flex-grow: 1`
160
168
  const grow = (
161
- this.componentWidthOperator === '>' ||
162
- this.componentWidth === 'fill'
169
+ this.widthOperator === '>' ||
170
+ this.width === 'fill'
163
171
  )
164
172
  // Interpret '<50%' as '50%, flex-shrink: 1`
165
- const shrink = this.componentWidthOperator === '<'
173
+ const shrink = this.widthOperator === '<'
166
174
  return {
167
175
  flex: `${grow ? 1 : 0} ${shrink ? 1 : 0} ${this.componentBasis}`
168
176
  }
@@ -174,7 +182,7 @@ export default DitoComponent.component('DitoContainer', {
174
182
  // TODO: BEM?
175
183
  'dito-single': this.single,
176
184
  'dito-disabled': this.componentDisabled,
177
- 'dito-width-fill': !basisIsAuto || this.componentWidth === 'fill',
185
+ 'dito-width-fill': !basisIsAuto || this.width === 'fill',
178
186
  'dito-has-errors': !!this.errors
179
187
  }
180
188
  }
@@ -226,13 +234,15 @@ export default DitoComponent.component('DitoContainer', {
226
234
  --justify: flex-end;
227
235
  }
228
236
 
229
- // Now only apply alignment if there are neighbouring components no the same
230
- // row that also align.
231
- // Look ahead:
232
- &:not(#{$self}--last-in-row) + #{&}:not(#{$self}--first-in-row),
233
- // Look behind:
234
- &:not(#{$self}--last-in-row):has(+ #{&}:not(#{$self}--first-in-row)) {
235
- justify-content: var(--justify);
237
+ &:not(#{$self}--only-in-row) {
238
+ // Now only apply alignment if there are neighbouring components no the
239
+ // same row that also align.
240
+ // Look ahead:
241
+ &:not(#{$self}--last-in-row) + #{&}:not(#{$self}--first-in-row),
242
+ // Look behind:
243
+ &:not(#{$self}--last-in-row):has(+ #{&}:not(#{$self}--first-in-row)) {
244
+ justify-content: var(--justify);
245
+ }
236
246
  }
237
247
  }
238
248
 
@@ -14,7 +14,7 @@
14
14
  v-for="(form, type) in forms"
15
15
  )
16
16
  a(
17
- v-if="shouldRender(form)"
17
+ v-if="shouldRenderSchema(form)"
18
18
  v-show="shouldShow(form)"
19
19
  :class="getFormClass(form, type)"
20
20
  @mousedown.stop="onPulldownMouseDown(type)"
@@ -61,7 +61,7 @@ export default DitoComponent.component('DitoCreateButton', {
61
61
 
62
62
  methods: {
63
63
  createItem(form, type = null) {
64
- if (this.shouldRender(form) && !this.shouldDisable(form)) {
64
+ if (this.shouldRenderSchema(form) && !this.shouldDisable(form)) {
65
65
  if (this.isInlined) {
66
66
  this.sourceComponent.createItem(form, type)
67
67
  } else {
@@ -79,6 +79,7 @@ export default DitoComponent.component('DitoDialog', {
79
79
 
80
80
  schema() {
81
81
  return {
82
+ type: 'dialog',
82
83
  components: this.components
83
84
  }
84
85
  },
@@ -283,6 +283,16 @@ export default DitoComponent.component('DitoForm', {
283
283
  },
284
284
 
285
285
  watch: {
286
+ $route(to, from) {
287
+ // Reload form data when navigating to a different entity in same form.
288
+ if (this.providesData) {
289
+ const { param } = this.meta
290
+ if (param && to.params[param] !== from.params[param]) {
291
+ this.loadData(true)
292
+ }
293
+ }
294
+ },
295
+
286
296
  sourceData: 'clearClonedData',
287
297
  // Needed for the 'create' redirect in `inheritedData()` to work:
288
298
  create: 'setupData'
@@ -9,22 +9,28 @@ component.dito-label(
9
9
  v-if="collapsible"
10
10
  :class="{ 'dito-opened': !collapsed }"
11
11
  )
12
- DitoElement.dito-label-prefix(
13
- v-for="(prefix, index) of prefixes"
14
- :key="`prefix-${index}`"
15
- tag="span"
16
- :content="prefix"
17
- )
18
- label(
19
- :for="dataPath"
20
- v-html="text"
21
- )
22
- DitoElement.dito-label-suffix(
23
- v-for="(suffix, index) of suffixes"
24
- :key="`suffix-${index}`"
25
- tag="span"
26
- :content="suffix"
27
- )
12
+ .dito-label__inner
13
+ DitoElement.dito-label-prefix(
14
+ v-for="(prefix, index) of prefixes"
15
+ :key="`prefix-${index}`"
16
+ tag="span"
17
+ :content="prefix"
18
+ )
19
+ label(
20
+ :for="dataPath"
21
+ v-html="text"
22
+ )
23
+ DitoElement.dito-label-suffix(
24
+ v-for="(suffix, index) of suffixes"
25
+ :key="`suffix-${index}`"
26
+ tag="span"
27
+ :content="suffix"
28
+ )
29
+ .dito-info(
30
+ v-if="info"
31
+ :data-tippy-content="info"
32
+ data-tippy-theme="info"
33
+ )
28
34
  slot(name="edit-buttons")
29
35
  </template>
30
36
 
@@ -40,7 +46,8 @@ export default DitoComponent.component('DitoLabel', {
40
46
  label: { type: [String, Object], default: null },
41
47
  dataPath: { type: String, default: null },
42
48
  collapsed: { type: Boolean, default: false },
43
- collapsible: { type: Boolean, default: false }
49
+ collapsible: { type: Boolean, default: false },
50
+ info: { type: String, default: null }
44
51
  },
45
52
 
46
53
  computed: {
@@ -83,6 +90,8 @@ export default DitoComponent.component('DitoLabel', {
83
90
  @import '../styles/_imports';
84
91
 
85
92
  .dito-label {
93
+ $self: &;
94
+
86
95
  --label-padding: 0;
87
96
  // For buttons and chevron to align right:
88
97
  display: flex;
@@ -92,8 +101,13 @@ export default DitoComponent.component('DitoLabel', {
92
101
  padding: var(--label-padding);
93
102
  margin: 0 0 $form-spacing-half 0;
94
103
 
104
+ &__inner {
105
+ display: flex;
106
+ // Stretch to full available width so that buttons appear right-aligned:
107
+ flex: 1 1 auto;
108
+ }
109
+
95
110
  label {
96
- display: inline;
97
111
  cursor: inherit;
98
112
  font-weight: bold;
99
113
  white-space: nowrap;
@@ -102,23 +116,14 @@ export default DitoComponent.component('DitoLabel', {
102
116
  label,
103
117
  .dito-label-prefix,
104
118
  .dito-label-suffix {
105
- &:nth-last-child(2) {
106
- // To stretch either label or .dito-label-suffix to full available width
107
- // so that buttons always appear right-aligned:
108
- flex: 1 1 auto;
109
- }
119
+ @include user-select(none);
120
+ @include ellipsis;
110
121
 
111
122
  &::after {
112
- content: '\a0'; // &nbsp;;
123
+ content: '\a0'; // &nbsp;
113
124
  }
114
125
  }
115
126
 
116
- .dito-label-prefix,
117
- .dito-label-suffix {
118
- @include user-select(none);
119
- @include ellipsis;
120
- }
121
-
122
127
  .dito-buttons {
123
128
  // Move the label padding inside .dito-buttons, so that it captures all
124
129
  // near mouse events:
@@ -131,11 +136,9 @@ export default DitoComponent.component('DitoLabel', {
131
136
  width: 100%;
132
137
  // In order for ellipsis to work on labels without affecting other layout,
133
138
  // we need to position it absolutely inside its container.
134
- label {
135
- @include ellipsis;
136
-
139
+ #{$self}__inner {
137
140
  position: absolute;
138
- max-width: 100%;
141
+ inset: 0;
139
142
  }
140
143
 
141
144
  &::after {
@@ -144,6 +147,10 @@ export default DitoComponent.component('DitoLabel', {
144
147
  content: '\200b'; // zero-width space;
145
148
  }
146
149
  }
150
+
151
+ .dito-info {
152
+ margin-left: 0.35em;
153
+ }
147
154
  }
148
155
 
149
156
  a.dito-label {
@@ -20,7 +20,7 @@
20
20
  v-if="schema.break === 'before'"
21
21
  )
22
22
  DitoContainer(
23
- v-if="shouldRender(schema)"
23
+ v-if="shouldRenderSchema(schema)"
24
24
  :key="nestedDataPath"
25
25
  v-resize="event => onResize(index, event)"
26
26
  :schema="schema"
@@ -1,6 +1,7 @@
1
1
  <template lang="pug">
2
2
  //- Only show panels in tabs when the tabs are also visible.
3
3
  component.dito-panel(
4
+ v-if="shouldRenderSchema(panelSchema)"
4
5
  v-show="visible && (!panelTabComponent || panelTabComponent.visible)"
5
6
  :is="panelTag"
6
7
  @submit.prevent
@@ -6,7 +6,7 @@
6
6
  v-for="{ schema, dataPath, tabComponent } in panels"
7
7
  )
8
8
  DitoPanel(
9
- v-if="shouldRender(schema)"
9
+ v-if="shouldRenderSchema(schema)"
10
10
  :key="getPanelKey(dataPath, tabComponent)"
11
11
  :schema="schema"
12
12
  :dataPath="dataPath"
@@ -34,6 +34,7 @@
34
34
  </template>
35
35
 
36
36
  <script>
37
+ import { delegate as tippyDelegate } from 'tippy.js'
37
38
  import { asArray, mapConcurrently, stripTags } from '@ditojs/utils'
38
39
  import DitoComponent from '../DitoComponent.js'
39
40
  import DitoUser from '../DitoUser.js'
@@ -90,6 +91,9 @@ export default DitoComponent.component('DitoRoot', {
90
91
  },
91
92
 
92
93
  async mounted() {
94
+ tippyDelegate(this.$el, {
95
+ target: '[data-tippy-content]'
96
+ })
93
97
  // Clear the label marked as active on all mouse and keyboard events, except
94
98
  // the ones that DitoLabel itself intercepts.
95
99
  this.domOn(document, {
@@ -172,7 +176,12 @@ export default DitoComponent.component('DitoRoot', {
172
176
  async (resolve, reject) => {
173
177
  // Process components to resolve async schemas.
174
178
  const routes = []
175
- await processSchemaComponents(this.api, { components }, routes, 0)
179
+ await processSchemaComponents(
180
+ this.api,
181
+ { type: 'dialog', components },
182
+ routes,
183
+ 0
184
+ )
176
185
  if (routes.length > 0) {
177
186
  throw new Error(
178
187
  'Dialogs do not support components that produce routes'
@@ -155,6 +155,7 @@ export default DitoComponent.component('DitoSchema', {
155
155
  ? data(this.context)
156
156
  : data
157
157
  ),
158
+ currentTab: null,
158
159
  componentsRegistry: {},
159
160
  panesRegistry: {},
160
161
  panelsRegistry: {}
@@ -187,17 +188,7 @@ export default DitoComponent.component('DitoSchema', {
187
188
  },
188
189
 
189
190
  selectedTab() {
190
- const currentTab = this.$route.hash?.slice(1) || null
191
- const tab =
192
- currentTab && this.shouldRender(this.tabs[currentTab])
193
- ? currentTab
194
- : this.defaultTab?.name || null
195
- if (tab !== currentTab) {
196
- // TODO: Move this watcher!
197
- // Any tab change needs to be reflected in the router also.
198
- this.$router.replace({ hash: `#${tab}` })
199
- }
200
- return tab
191
+ return this.currentTab || this.defaultTab?.name || null
201
192
  },
202
193
 
203
194
  defaultTab() {
@@ -336,6 +327,32 @@ export default DitoComponent.component('DitoSchema', {
336
327
  }
337
328
  },
338
329
 
330
+ watch: {
331
+ '$route.hash': {
332
+ immediate: true,
333
+ handler(hash) {
334
+ if (this.hasTabs) {
335
+ this.currentTab = hash?.slice(1) || null
336
+ }
337
+ }
338
+ },
339
+
340
+ 'selectedTab'(selectedTab) {
341
+ if (this.hasTabs) {
342
+ let tab = null
343
+ if (selectedTab !== this.currentTab) {
344
+ // Any tab change needs to be reflected in the router also.
345
+ tab = selectedTab
346
+ } else if (!this.shouldRenderSchema(this.tabs[selectedTab])) {
347
+ tab = this.defaultTab?.name
348
+ }
349
+ if (tab) {
350
+ this.$router.replace({ hash: `#${tab}` })
351
+ }
352
+ }
353
+ }
354
+ },
355
+
339
356
  created() {
340
357
  this._register(true)
341
358
  this.setupSchemaFields()
@@ -6,7 +6,7 @@ nav.dito-sidebar.dito-scroll-parent
6
6
  v-for="view in views"
7
7
  )
8
8
  RouterLink(
9
- v-if="shouldRender(view)"
9
+ v-if="shouldRenderSchema(view)"
10
10
  v-slot="{ isActive, href, route }"
11
11
  custom
12
12
  :to="`/${view.path}`"
@@ -5,7 +5,7 @@ thead.dito-table-head
5
5
  v-for="column in columns"
6
6
  )
7
7
  th(
8
- v-if="shouldRender(column)"
8
+ v-if="shouldRenderSchema(column)"
9
9
  :class="getColumnClass(column)"
10
10
  )
11
11
  RouterLink(
@@ -4,7 +4,7 @@
4
4
  v-for="(tabSchema, key) in tabs"
5
5
  )
6
6
  RouterLink.dito-link(
7
- v-if="shouldRender(tabSchema)"
7
+ v-if="shouldRenderSchema(tabSchema)"
8
8
  :key="key"
9
9
  :to="{ hash: `#${key}` }"
10
10
  :class="{ 'dito-active': selectedTab === key }"
@@ -11,7 +11,7 @@ template(
11
11
  :key="name"
12
12
  )
13
13
  .dito-view.dito-scroll-parent(
14
- v-else-if="shouldRender(viewSchema)"
14
+ v-else-if="shouldRenderSchema(viewSchema)"
15
15
  :data-resource="sourceSchema.path"
16
16
  )
17
17
  DitoSchema(
@@ -28,7 +28,10 @@ template(
28
28
  <script>
29
29
  import DitoComponent from '../DitoComponent.js'
30
30
  import RouteMixin from '../mixins/RouteMixin.js'
31
- import { someSchemaComponent, isSingleComponentView } from '../utils/schema.js'
31
+ import {
32
+ isSingleComponentView,
33
+ someNestedSchemaComponent
34
+ } from '../utils/schema.js'
32
35
  import { hasResource } from '../utils/resource.js'
33
36
 
34
37
  // @vue/component
@@ -93,7 +96,7 @@ export default DitoComponent.component('DitoView', {
93
96
  },
94
97
 
95
98
  providesData() {
96
- return someSchemaComponent(this.viewSchema, hasResource)
99
+ return someNestedSchemaComponent(this.viewSchema, hasResource)
97
100
  }
98
101
  },
99
102
 
@@ -1,11 +1,8 @@
1
1
  import {
2
2
  isObject,
3
- isArray,
4
3
  isString,
5
4
  isFunction,
6
- asArray,
7
5
  equals,
8
- getValueAtDataPath,
9
6
  labelize,
10
7
  hyphenate,
11
8
  format
@@ -13,7 +10,7 @@ import {
13
10
  import appState from '../appState.js'
14
11
  import DitoContext from '../DitoContext.js'
15
12
  import EmitterMixin from './EmitterMixin.js'
16
- import { isMatchingType, convertType } from '../utils/type.js'
13
+ import { getSchemaValue, shouldRenderSchema } from '../utils/schema.js'
17
14
  import { getResource, getMemberResource } from '../utils/resource.js'
18
15
  import { computed, reactive } from 'vue'
19
16
 
@@ -201,43 +198,15 @@ export default {
201
198
 
202
199
  getSchemaValue(
203
200
  keyOrDataPath,
204
- { type, default: def, schema = this.schema, callback = true } = {}
201
+ { type, schema = this.schema, callback = true, default: def } = {}
205
202
  ) {
206
- const types = type && asArray(type)
207
- // For performance reasons, data-paths in `keyOrDataPath` can only be
208
- // provided in in array format here:
209
- let value = schema
210
- ? isArray(keyOrDataPath)
211
- ? getValueAtDataPath(schema, keyOrDataPath, () => undefined)
212
- : schema[keyOrDataPath]
213
- : undefined
214
-
215
- if (value === undefined && def !== undefined) {
216
- if (callback && isFunction(def) && !isMatchingType(types, def)) {
217
- // Support `default()` functions for any type except `Function`:
218
- def = def.call(this)
219
- }
220
- return def
221
- }
222
-
223
- if (isMatchingType(types, value)) {
224
- return value
225
- }
226
- // Any schema value handled through `getSchemaValue()` can provide
227
- // a function that's resolved when the value is evaluated:
228
- if (callback && isFunction(value)) {
229
- value = value(this.context)
230
- }
231
- // Now finally see if we can convert to the expect types.
232
- if (types && value != null && !isMatchingType(types, value)) {
233
- for (const type of types) {
234
- const converted = convertType(type, value)
235
- if (converted !== value) {
236
- return converted
237
- }
238
- }
239
- }
240
- return value
203
+ return getSchemaValue(keyOrDataPath, {
204
+ type,
205
+ schema,
206
+ callback,
207
+ context: this.context,
208
+ default: isFunction(def) ? () => def.call(this) : def
209
+ })
241
210
  },
242
211
 
243
212
  getLabel(schema, name) {
@@ -263,12 +232,8 @@ export default {
263
232
  }
264
233
  },
265
234
 
266
- shouldRender(schema = null) {
267
- return this.getSchemaValue('if', {
268
- type: Boolean,
269
- default: true,
270
- schema
271
- })
235
+ shouldRenderSchema(schema = null) {
236
+ return shouldRenderSchema(schema, this.context)
272
237
  },
273
238
 
274
239
  shouldShow(schema = null) {
@@ -314,7 +314,7 @@ export default {
314
314
 
315
315
  watch: {
316
316
  $route(to, from) {
317
- if (from.path === to.path && from.hash === to.hash) {
317
+ if (this.providesData && from.path === to.path && from.hash === to.hash) {
318
318
  // Paths and hashes remain the same, so only queries have changed.
319
319
  // Update filter and reload data without clearing.
320
320
  this.query = to.query
@@ -3,7 +3,7 @@ import ValidationMixin from './ValidationMixin.js'
3
3
  import { getSchemaAccessor } from '../utils/accessor.js'
4
4
  import { computeValue } from '../utils/schema.js'
5
5
  import { getItem, getParentItem } from '../utils/data.js'
6
- import { isString, asArray, camelize } from '@ditojs/utils'
6
+ import { asArray, camelize } from '@ditojs/utils'
7
7
 
8
8
  // @vue/component
9
9
  export default {
@@ -17,6 +17,8 @@ export default {
17
17
  data: { type: [Object, Array], required: true },
18
18
  meta: { type: Object, required: true },
19
19
  store: { type: Object, required: true },
20
+ width: { type: [Number, String], default: null },
21
+ label: { type: String, default: null },
20
22
  single: { type: Boolean, default: false },
21
23
  nested: { type: Boolean, default: true },
22
24
  disabled: { type: Boolean, default: false }
@@ -109,21 +111,6 @@ export default {
109
111
  return this.processedData
110
112
  },
111
113
 
112
- label: getSchemaAccessor('label', {
113
- type: [String, Boolean],
114
- get(label) {
115
- return isString(label)
116
- ? label
117
- : label !== false && this.$options.generateLabel
118
- ? this.getLabel(this.schema)
119
- : null
120
- }
121
- }),
122
-
123
- width: getSchemaAccessor('width', {
124
- type: [String, Number]
125
- }),
126
-
127
114
  visible: getSchemaAccessor('visible', {
128
115
  type: Boolean,
129
116
  default() {
@@ -0,0 +1,32 @@
1
+ .dito-info {
2
+ --size: calc(1em * var(--line-height));
3
+
4
+ display: inline-block;
5
+ position: relative;
6
+ flex: 0 0 var(--size);
7
+ width: var(--size);
8
+ height: var(--size);
9
+ text-align: center;
10
+ vertical-align: top;
11
+ margin-left: 0.35em;
12
+ color: $color-active;
13
+
14
+ &::before {
15
+ @extend %icon-info;
16
+
17
+ width: var(--size);
18
+ }
19
+ }
20
+
21
+ .tippy-box[data-theme~='info'] {
22
+ background-color: $color-active;
23
+ color: $color-white;
24
+
25
+ > .tippy-arrow::before {
26
+ color: $color-active;
27
+ }
28
+
29
+ .tippy-content {
30
+ white-space: pre-line;
31
+ }
32
+ }