wave-ui 2.47.0 → 3.0.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 (50) hide show
  1. package/dist/wave-ui.cjs.js +1 -1
  2. package/dist/wave-ui.css +1 -1
  3. package/dist/wave-ui.es.js +1920 -1638
  4. package/dist/wave-ui.umd.js +1 -1
  5. package/package.json +4 -3
  6. package/src/wave-ui/components/index.js +1 -0
  7. package/src/wave-ui/components/transitions/w-transition-expand.vue +26 -15
  8. package/src/wave-ui/components/w-accordion.vue +8 -2
  9. package/src/wave-ui/components/w-alert.vue +10 -4
  10. package/src/wave-ui/components/w-app.vue +2 -107
  11. package/src/wave-ui/components/w-badge.vue +7 -3
  12. package/src/wave-ui/components/w-button/button.vue +6 -2
  13. package/src/wave-ui/components/w-card.vue +14 -4
  14. package/src/wave-ui/components/w-checkbox.vue +15 -8
  15. package/src/wave-ui/components/w-confirm.vue +7 -2
  16. package/src/wave-ui/components/w-date-picker.vue +6 -0
  17. package/src/wave-ui/components/w-dialog.vue +9 -3
  18. package/src/wave-ui/components/w-divider.vue +9 -3
  19. package/src/wave-ui/components/w-drawer.vue +9 -3
  20. package/src/wave-ui/components/w-input.vue +4 -2
  21. package/src/wave-ui/components/w-list.vue +1 -1
  22. package/src/wave-ui/components/w-menu.vue +11 -4
  23. package/src/wave-ui/components/w-notification-manager.vue +18 -30
  24. package/src/wave-ui/components/w-notification.vue +7 -1
  25. package/src/wave-ui/components/w-progress.vue +2 -2
  26. package/src/wave-ui/components/w-radio.vue +8 -2
  27. package/src/wave-ui/components/w-rating.vue +11 -3
  28. package/src/wave-ui/components/w-scrollbar.vue +24 -0
  29. package/src/wave-ui/components/w-select.vue +18 -8
  30. package/src/wave-ui/components/w-slider.vue +13 -7
  31. package/src/wave-ui/components/w-steps.vue +14 -4
  32. package/src/wave-ui/components/w-switch.vue +14 -14
  33. package/src/wave-ui/components/w-table.vue +107 -16
  34. package/src/wave-ui/components/w-tabs/index.vue +8 -2
  35. package/src/wave-ui/components/w-tag.vue +15 -10
  36. package/src/wave-ui/components/w-textarea.vue +6 -2
  37. package/src/wave-ui/components/w-timeline.vue +18 -5
  38. package/src/wave-ui/components/w-toolbar.vue +8 -2
  39. package/src/wave-ui/components/w-tooltip.vue +10 -4
  40. package/src/wave-ui/components/w-tree.vue +57 -19
  41. package/src/wave-ui/core.js +117 -90
  42. package/src/wave-ui/scss/_base.scss +53 -2
  43. package/src/wave-ui/scss/_colors.scss +41 -17
  44. package/src/wave-ui/scss/_layout.scss +5 -12
  45. package/src/wave-ui/scss/_mixins.scss +24 -0
  46. package/src/wave-ui/scss/_variables.scss +100 -11
  47. package/src/wave-ui/utils/colors.js +60 -3
  48. package/src/wave-ui/utils/config.js +35 -11
  49. package/src/wave-ui/utils/dynamic-css.js +92 -30
  50. package/src/wave-ui/utils/notification-manager.js +39 -8
@@ -4,22 +4,27 @@ ul.w-tree(:class="classes")
4
4
  v-for="(item, i) in currentDepthItems"
5
5
  :key="i"
6
6
  :class="itemClasses(item)")
7
- .w-tree__item-label(
8
- @click="!disabled && onLabelClick(item, $event)"
9
- @keydown="!disabled && onLabelKeydown(item, $event)"
10
- :tabindex="!disabled && (item.children || item.branch || selectable) && !(unexpandableEmpty && !item.children) ? 0 : null")
7
+ //- The keys `route` & `disabled` are always present in any currentDepthItems.
8
+ component.w-tree__item-label(
9
+ :is="!disabled && !item.disabled && item.route ? (!$router || hasExternalLink(item) ? 'a' : 'router-link') : 'div'"
10
+ v-bind="item.route && { [!$router || hasExternalLink(item) ? 'href' : 'to']: item.route }"
11
+ @click="!disabled && !item.disabled && onLabelClick(item, $event)"
12
+ @keydown="!disabled && !item.disabled && onLabelKeydown(item, $event)"
13
+ :tabindex="!disabled && !item.disabled && (item.children || item.branch || selectable) && !(unexpandableEmpty && !item.children) ? 0 : null")
14
+ //- @click.stop to not follow link if item is a link.
11
15
  w-button.w-tree__item-expand(
12
16
  v-if="(item.children || item.branch) && ((expandOpenIcon && item.open) || expandIcon) && !(unexpandableEmpty && !item.children)"
17
+ @click.stop="!disabled && !item.disabled && onLabelClick(item, $event)"
13
18
  color="inherit"
14
19
  :icon="(item.open && expandOpenIcon) || expandIcon"
15
20
  :icon-props="{ rotate90a: !item.open }"
16
21
  :tabindex="-1"
17
- :disabled="disabled"
22
+ :disabled="disabled || item.disabled"
18
23
  text
19
24
  sm)
20
- slot(name="item-label" :item="item.originalItem" :depth="depth" :open="item.open")
21
- w-icon(v-if="itemIcon(item)" class="w-tree__item-icon") {{ itemIcon(item) }}
22
- span {{ item.label }}
25
+ slot(name="item" :item="item.originalItem" :depth="depth" :open="item.open")
26
+ w-icon(v-if="itemIcon(item)" class="w-tree__item-icon" :color="item.originalItem[itemIconColorKey] || iconColor") {{ itemIcon(item) }}
27
+ span(v-html="item.label")
23
28
  span.ml1(v-if="counts && (item.children || item.branch)").
24
29
  ({{ item.originalItem.children?.length || 0 }})
25
30
  component(
@@ -39,16 +44,15 @@ ul.w-tree(:class="classes")
39
44
  @click="$emit('click', $event)"
40
45
  @select="$emit('select', $event)"
41
46
  @update:model-value="$emit('update:model-value', $event)")
42
- template(#item-label="{ item, depth, open }")
43
- slot(name="item-label" :item="item" :depth="depth" :open="open")
47
+ template(#item="{ item, depth, open }")
48
+ slot(name="item" :item="item" :depth="depth" :open="open")
44
49
  </template>
45
50
 
46
51
  <script>
52
+ import { consoleWarn } from '../utils/console'
47
53
  /**
48
- * @todo things to support:
49
- * - items routes
50
- * - icon per item
51
- * - left border?
54
+ * @todo:
55
+ * - option to add a left border.
52
56
  **/
53
57
 
54
58
  export default {
@@ -70,8 +74,14 @@ export default {
70
74
  noTransition: { type: Boolean },
71
75
  selectable: { type: Boolean },
72
76
  // By default it only reacts to items count change (added or deleted items) not property of items change.
73
- depthReactivity: { type: Boolean },
74
- counts: { type: Boolean }
77
+ deepReactivity: { type: Boolean },
78
+ counts: { type: Boolean },
79
+ itemIconKey: { type: String, default: 'icon' }, // Support a different icon per item.
80
+ iconColor: { type: String }, // Applies a color on all the label item icons.
81
+ itemIconColorKey: { type: String, default: 'iconColor' }, // Applies a specific color on each label item icons.
82
+ itemRouteKey: { type: String, default: 'route' }, // Uses a router link if the item has the `route` key.
83
+ itemDisabledKey: { type: String, default: 'disabled' }, // Disables the item click and selection.
84
+ itemOpenKey: { type: String, default: 'open' } // Open the item by default.
75
85
  },
76
86
 
77
87
  emits: ['update:model-value', 'before-open', 'open', 'before-close', 'close', 'click', 'select'],
@@ -97,6 +107,12 @@ export default {
97
107
  updateCurrentDepthTree (items, oldItems = []) {
98
108
  this.currentDepthItems = []
99
109
 
110
+ if (!Array.isArray(items) && typeof items !== 'object') {
111
+ return consoleWarn(`[w-tree] the tree items must be of type array or object, ${typeof items} received.`, items)
112
+ }
113
+
114
+ if (!Array.isArray(items)) items = [items]
115
+
100
116
  items.forEach((item, i) => {
101
117
  this.currentDepthItems.push({
102
118
  originalItem: item, // Store the original item to return it on event emits.
@@ -104,8 +120,10 @@ export default {
104
120
  label: item.label,
105
121
  children: !!item.children, // The children tree remains available in originalItem.
106
122
  branch: item.branch,
123
+ route: item[this.itemRouteKey],
124
+ disabled: item[this.itemDisabledKey],
107
125
  depth: this.depth,
108
- open: oldItems[i]?.open || false
126
+ open: !!(oldItems[i]?.open || this.expandAll || item[this.itemOpenKey])
109
127
  })
110
128
  })
111
129
  },
@@ -132,6 +150,9 @@ export default {
132
150
  },
133
151
 
134
152
  onLabelClick (item, e) {
153
+ const route = item[this.itemRouteKey]
154
+ if (route && this.$router && !this.hasExternalLink(item)) e.preventDefault()
155
+
135
156
  this.$emit('click', { item: item.originalItem, depth: this.depth, e })
136
157
  if (item.children || (item.branch && !this.unexpandableEmpty)) this.expandDepth(item)
137
158
 
@@ -168,6 +189,7 @@ export default {
168
189
  treeTabbableItems[i + indexModifier] && treeTabbableItems[i + indexModifier].focus()
169
190
  return true // Break the loop.
170
191
  }
192
+ return false
171
193
  })
172
194
  }
173
195
  }
@@ -182,6 +204,7 @@ export default {
182
204
  * @param {String} selector any valid DOM selector to match the siblings.
183
205
  */
184
206
  getPreviousSibling (node, selector) {
207
+ // eslint-disable-next-line no-unmodified-loop-condition
185
208
  while (selector && (node = node.previousElementSibling)) {
186
209
  if (node.matches(selector)) return node
187
210
  }
@@ -195,6 +218,7 @@ export default {
195
218
  * @param {String} selector any valid DOM selector to match the siblings.
196
219
  */
197
220
  getNextSibling (node, selector) {
221
+ // eslint-disable-next-line no-unmodified-loop-condition
198
222
  while (selector && (node = node.nextElementSibling)) {
199
223
  if (node.matches(selector)) return node
200
224
  }
@@ -207,15 +231,20 @@ export default {
207
231
 
208
232
  itemIcon (item) {
209
233
  return (
210
- item.originalItem.icon ||
234
+ item.originalItem[this.itemIconKey] ||
211
235
  (!item.children && !item.branch && this.leafIcon) ||
212
236
  ((item.children || item.branch) && ((item.open && this.branchOpenIcon) || this.branchIcon))
213
237
  )
214
238
  },
215
239
 
240
+ hasExternalLink (item) {
241
+ return /^(https?:)?\/\/|mailto:|tel:/.test(item[this.itemRouteKey])
242
+ },
243
+
216
244
  itemClasses (item) {
217
245
  return {
218
246
  [item.children || item.branch ? 'w-tree__item--branch' : 'w-tree__item--leaf']: true,
247
+ 'w-tree__item--disabled': item[this.itemDisabledKey],
219
248
  'w-tree__item--empty': item.branch && !item.children,
220
249
  'w-tree__item--unexpandable': item.branch && !item.children && this.unexpandableEmpty
221
250
  }
@@ -229,7 +258,7 @@ export default {
229
258
  // The open property of each item has to be retained from this.currentDepthItems in order to stay
230
259
  // in the same state after DOM repaint.
231
260
  items => this.updateCurrentDepthTree(items, this.currentDepthItems),
232
- { deep: !!this.depthReactivity } // Deep watching is more resource consuming. Only enable on user demand.
261
+ { deep: !!this.deepReactivity } // Deep watching is more resource consuming. Only enable on user demand.
233
262
  )
234
263
  },
235
264
 
@@ -258,6 +287,7 @@ $expand-icon-size: 20px;
258
287
  position: relative;
259
288
  display: inline-flex;
260
289
  align-items: center;
290
+ user-select: none;
261
291
 
262
292
  &:before {
263
293
  content: '';
@@ -268,16 +298,24 @@ $expand-icon-size: 20px;
268
298
  right: - $base-increment - 2px;
269
299
  border-radius: $border-radius;
270
300
  }
301
+ &:hover:before {background-color: rgba($primary, 0.05);}
271
302
  &:focus:before {background-color: rgba($primary, 0.1);}
272
303
  }
273
304
  &__item--leaf &__item-label:before {
274
305
  left: - $base-increment;
275
306
  right: - $base-increment;
276
307
  }
308
+ &__item--disabled &__item-label {opacity: 0.5;}
309
+ &__item--disabled &__item-label:before {display: none;}
277
310
 
278
311
  &__item-expand {margin-right: 2px;}
279
312
 
280
313
  &__item--branch > &__item-label {cursor: pointer;}
314
+ &__item--disabled > &__item-label {
315
+ color: $disabled-color;
316
+ cursor: not-allowed;
317
+ -webkit-tap-highlight-color: transparent;
318
+ }
281
319
  &__item--unexpandable > &__item-label {
282
320
  margin-left: $expand-icon-size + 2px;
283
321
  cursor: auto;
@@ -1,52 +1,73 @@
1
1
  import { reactive, inject } from 'vue'
2
- import config, { mergeConfig } from './utils/config'
3
- import NotificationManager from './utils/notification-manager'
4
- import colors from './utils/colors'
5
- // import * as directives from './directives'
6
-
7
- const shadeColor = (color, amount) => {
8
- return '#' + color.slice(1).match(/../g)
9
- .map(x => (x =+ `0x${x}` + amount, x < 0 ? 0 : ( x > 255 ? 255 : x)).toString(16).padStart(2, 0))
10
- .join('')
2
+ import { mergeConfig } from './utils/config'
3
+ import { injectNotifManagerInDOM, NotificationManager } from './utils/notification-manager'
4
+ import { colorPalette, generateColorShades, flattenColors } from './utils/colors'
5
+ import { injectColorsCSSInDOM, injectCSSInDOM } from './utils/dynamic-css'
6
+ import './scss/index.scss'
7
+
8
+ let mounted = false
9
+ const detectOSDarkMode = $waveui => {
10
+ const matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
11
+ $waveui.preferredTheme = matchMedia.matches ? 'dark' : 'light'
12
+ $waveui.switchTheme($waveui.preferredTheme)
13
+
14
+ matchMedia.addEventListener('change', event => {
15
+ $waveui.preferredTheme = event.matches ? 'dark' : 'light'
16
+ $waveui.switchTheme($waveui.preferredTheme)
17
+ })
11
18
  }
12
19
 
13
- // Keep the notification manager private.
14
- // @todo: find a way to use private fields with Vue 3 proxies.
15
- // https://github.com/tc39/proposal-class-fields/issues/106
16
- // https://github.com/tc39/proposal-class-fields/issues/227
17
- let notificationManager = null
20
+ /**
21
+ * Inject presets into a Vue component props defaults before its registration into the app.
22
+ *
23
+ * @param {Object} component the Vue component to inject presets into.
24
+ * @param {Object} presets the presets to inject. E.g. `{ bgColor: 'green' }`.
25
+ */
26
+ const injectPresets = (component, presets) => {
27
+ for (const preset in presets) {
28
+ component.props[preset].default = presets[preset]
29
+ }
30
+ }
18
31
 
19
32
  export default class WaveUI {
20
- static instance = null
21
- static vueInstance = null // Needed until constructor is called.
22
- // #notificationManager
23
-
24
- // Public breakpoint object. Accessible from this.$waveui.breakpoint.
25
- breakpoint = {
26
- name: '',
27
- xs: false,
28
- sm: false,
29
- md: false,
30
- lg: false,
31
- xl: false
33
+ static #registered = false
34
+
35
+ // Exposed as a global object and also `app.provide`d.
36
+ // Accessible from this.$waveui, or inject('$waveui').
37
+ $waveui = {
38
+ breakpoint: {
39
+ name: '',
40
+ xs: false,
41
+ sm: false,
42
+ md: false,
43
+ lg: false,
44
+ xl: false,
45
+ width: null
46
+ },
47
+ config: {},
48
+ colors: {}, // Object of pairs of color-name => color hex.
49
+ preferredTheme: null, // The user OS preferred theme (light or dark).
50
+ theme: null, // The current theme (light or dark).
51
+ _notificationManager: null,
52
+
53
+ // Callable from this.$waveui.
54
+ notify (...args) {
55
+ this._notificationManager.notify(...args)
56
+ },
57
+
58
+ // Callable from this.$waveui.
59
+ switchTheme (theme) {
60
+ this.theme = theme
61
+ document.documentElement.setAttribute('data-theme', theme)
62
+ document.head.querySelector('#wave-ui-colors')?.remove?.()
63
+ const themeColors = this.config.colors[this.theme]
64
+ injectColorsCSSInDOM(themeColors)
65
+ this.colors = flattenColors(themeColors, colorPalette)
66
+ }
32
67
  }
33
68
 
34
- // A public object containing pairs of color-name => color hex.
35
- // Accessible from anywhere via `this.$waveui.colors`.
36
- // These colors generate the CSS in `w-app` on mounted.
37
- colors = colors.reduce((obj, color) => {
38
- obj[color.label] = color.color
39
- color.shades.forEach(shade => (obj[shade.label] = shade.color))
40
- return obj
41
- }, { ...config.colors, black: '#000', white: '#fff', transparent: 'transparent', inherit: 'inherit' })
42
-
43
- config = {} // Store and expose the config in the $waveui object.
44
-
45
69
  static install (app, options = {}) {
46
70
  // Register directives.
47
- // for (const id in directives) {
48
- // if (directives[id]) app.directive(id, directives[id])
49
- // }
50
71
  app.directive('focus', {
51
72
  // Wait for the next tick to focus the newly mounted element.
52
73
  mounted: el => setTimeout(() => el.focus(), 0)
@@ -62,71 +83,77 @@ export default class WaveUI {
62
83
 
63
84
  // Register a-la-carte components from the given list.
64
85
  const { components = {} } = options || {}
65
- for (let id in components) {
86
+ for (const id in components) {
66
87
  const component = components[id]
88
+ // If presets are defined for this component inject them into the props defaults.
89
+ if (options.presets?.[component.name]) injectPresets(component, options.presets[component.name])
67
90
  app.component(component.name, component)
68
91
  }
69
92
 
70
93
  // Register mixins.
71
- // app.mixin({
72
- // mounted () {
73
- // }
74
- // })
94
+ app.mixin({
95
+ // Add a mixin to capture the first mounted hook, trigger the Wave UI init then unregister the mixin straight away.
96
+ beforeMount () {
97
+ if (!mounted) {
98
+ mounted = true
99
+ const $waveui = inject('$waveui')
100
+ const { config } = $waveui
101
+
102
+ // Add the .w-app class where defined by user or at the root.
103
+ const wApp = document.querySelector(config.on) || document.body
104
+ wApp.classList.add('w-app')
105
+
106
+ let themeColors = config.colors[config.theme]
107
+ if (config.theme === 'auto') {
108
+ detectOSDarkMode($waveui)
109
+ themeColors = config.colors[$waveui.preferredTheme]
110
+ $waveui.colors = flattenColors(themeColors, colorPalette)
111
+ }
112
+ injectColorsCSSInDOM(themeColors)
113
+ injectCSSInDOM($waveui)
114
+ injectNotifManagerInDOM(wApp, components, $waveui)
115
+
116
+ // This mixin must only run once, we can delete it.
117
+ app._context.mixins.find(mixin => mixin.mounted && delete mixin.mounted)
118
+ }
119
+ }
120
+ })
75
121
 
76
- WaveUI.registered = true
122
+ new WaveUI(app, options)
123
+ WaveUI.#registered = true
77
124
  }
78
125
 
79
- // Singleton.
80
126
  constructor (app, options = {}) {
81
- if (WaveUI.instance) return WaveUI.instance
82
-
83
- else {
84
- if (!WaveUI.registered) app.use(WaveUI)
85
- notificationManager = reactive(new NotificationManager())
86
-
87
- // Merge user options into the default config.
88
- mergeConfig(options)
89
-
90
- // Add color shades for each custom color given in options.
91
- if (config.css.colorShades) {
92
- config.colorShades = {}
127
+ if (WaveUI.#registered) {
128
+ console.warn('Wave UI is already instantiated.')
129
+ return
130
+ }
93
131
 
94
- for (let color in config.colors) {
95
- color = { label: color, color: config.colors[color].replace('#', '') }
96
- const col = color.color
97
- if (col.length === 3) color.color = col[0] + '' + col[0] + col[1] + col[1] + col[2] + col[2]
132
+ this.$waveui._notificationManager = new NotificationManager()
98
133
 
99
- this.colors[color.label] = `#${color.color}`
134
+ if (!options.theme) options.theme = 'light'
135
+ // Move colors inside a theme if there are option.colors without theme.
136
+ // E.g. colors: { primary, ... } & not colors: { light { primary, ... }, dark: { primary, ... } })
137
+ const colors = { ...options.colors }
138
+ if (!options.colors?.light) options.colors.light = colors
139
+ if (!options.colors?.dark) options.colors.dark = colors
140
+ // Cleanup anything else than themes in config.colors.
141
+ options.colors = { light: options.colors.light, dark: options.colors.dark }
100
142
 
101
- for (let i = 1; i <= 3; i++) {
102
- const lighterColor = shadeColor(`#${color.color}`, i * 40)
103
- const darkerColor = shadeColor(`#${color.color}`, -i * 40)
104
- this.colors[`${color.label}-light${i}`] = lighterColor
105
- this.colors[`${color.label}-dark${i}`] = darkerColor
143
+ // Merge user options into the default config.
144
+ let { components, ...config } = options
145
+ config = this.$waveui.config = mergeConfig(config)
106
146
 
107
- // Adding the shades to the config object to generate the CSS from w-app.
108
- config.colorShades[`${color.label}-light${i}`] = lighterColor
109
- config.colorShades[`${color.label}-dark${i}`] = darkerColor
110
- }
111
- }
112
- }
147
+ // Generates color shades for each color of each theme and store in the config.colors object.
148
+ if (config.css.colorShades) generateColorShades(config)
113
149
 
114
- this.config = config
115
- this.notify = (...args) => notificationManager.notify(...args)
116
- WaveUI.instance = this
150
+ // Make Wave UI reactive and expose the single instance in the app.
151
+ const $waveui = reactive(this.$waveui)
152
+ app.config.globalProperties.$waveui = $waveui
153
+ app.provide('$waveui', $waveui)
117
154
 
118
- // Make waveui reactive and expose the single instance in Vue.
119
- app.config.globalProperties.$waveui = reactive(this)
120
- app.provide('$waveui', WaveUI.instance)
155
+ if (config.theme !== 'auto') {
156
+ this.$waveui.colors = flattenColors(config.colors[config.theme], colorPalette)
121
157
  }
122
158
  }
123
-
124
- notify (...args) {
125
- notificationManager.notify(...args)
126
- }
127
159
  }
128
-
129
- /**
130
- * Returns the WaveUI instance. Equivalent to using `$waveui` inside templates.
131
- */
132
- export const useWaveUI = () => inject('$waveui')
@@ -1,10 +1,61 @@
1
- * {outline: none;margin: 0;padding: 0;}
1
+ @use "sass:map";
2
+
3
+ // The CSS variables are used in the dynamic-css.js file in order to reuse the same SCSS
4
+ // variable presets.
5
+ :root {
6
+ --w-base-increment: #{$base-increment};
7
+ --w-css-scope: #{$css-scope};
8
+
9
+ background-color: rgb(var(--w-base-bg-color-rgb));
10
+ color: rgb(var(--w-base-color-rgb));
11
+ }
12
+
13
+ :root[data-theme="light"] {
14
+ --w-base-bg-color-rgb: #{map.get($theme-light, 'base-bg-color-rgb')};
15
+ --w-base-color-rgb: #{map.get($theme-light, 'base-color-rgb')};
16
+ --w-contrast-bg-color-rgb: #{map.get($theme-light, 'contrast-bg-color-rgb')};
17
+ --w-contrast-color-rgb: #{map.get($theme-light, 'contrast-color-rgb')};
18
+ --w-disabled-color-rgb: #{map.get($theme-light, 'disabled-color-rgb')};
19
+ }
20
+
21
+ :root[data-theme="dark"] {
22
+ --w-base-bg-color-rgb: #{map.get($theme-dark, 'base-bg-color-rgb')};
23
+ --w-base-color-rgb: #{map.get($theme-dark, 'base-color-rgb')};
24
+ --w-contrast-bg-color-rgb: #{map.get($theme-dark, 'contrast-bg-color-rgb')};
25
+ --w-contrast-color-rgb: #{map.get($theme-dark, 'contrast-color-rgb')};
26
+ --w-disabled-color-rgb: #{map.get($theme-dark, 'disabled-color-rgb')};
27
+ }
28
+
29
+ * {
30
+ outline: none;
31
+ margin: 0;
32
+ padding: 0;
33
+ }
2
34
 
3
35
  body {overflow-x: hidden;}
4
36
 
5
37
  a {text-decoration: none;}
6
38
 
7
- .w-app, .w-app *, .w-app :before, .w-app :after {box-sizing: border-box;}
39
+ .w-app {
40
+ position: relative; // Make the .w-app a referential for tooltips / menus.
41
+ display: flex;
42
+ flex-direction: column;
43
+ min-height: 100vh;
44
+
45
+ &, *, :before, :after {box-sizing: border-box;}
46
+
47
+ &.row {flex-direction: row;}
48
+ &.d-block {display: block;}
49
+ &.align-center {align-items: center;}
50
+ &.align-end {align-items: flex-end;}
51
+ &.justify-center {justify-content: center;}
52
+ &.justify-end {justify-content: flex-end;}
53
+ &.justify-space-between {justify-content: space-between;}
54
+ &.justify-space-around {justify-content: space-around;}
55
+ &.justify-space-evenly {justify-content: space-evenly;}
56
+ &.text-center {text-align: center;}
57
+ &.text-right {text-align: right;}
58
+ }
8
59
 
9
60
  .w-main {
10
61
  padding-left: 3 * $base-increment;
@@ -1,21 +1,7 @@
1
1
  #{$css-scope} {
2
- .primary--bg,
3
- .primary-dark1--bg,
4
- .primary-dark2--bg,
5
- .primary-dark3--bg {color: #fff;}
6
- .white--bg {background-color: #fff;}
7
- .white {color: #fff;}
8
- .grey--bg {background-color: #888;}
9
- .grey {color: #888;}
10
- .black--bg {background-color: #000;}
11
- .black {color: #000;}
12
- .transparent--bg {background-color: transparent;}
13
- .transparent {color: transparent;}
14
- .inherit--bg {background-color: inherit;}
15
- .inherit {color: inherit;}
16
- .success--bg, .error--bg, .warning--bg, .info--bg {color: #fff;}
17
-
18
- $colors:(
2
+ // Color palette (immutable).
3
+ // ------------------------------------------------------
4
+ $colors: (
19
5
  'pink': #e91e63,
20
6
  'purple': #a741b9,
21
7
  'deep-purple': #673ab7,
@@ -37,6 +23,8 @@
37
23
  'grey': #848484
38
24
  );
39
25
 
26
+ // For each color, create a [color] and a [color]--bg associated classes,
27
+ // + 6 shades lighter and 6 shades darker.
40
28
  @each $label, $color in $colors {
41
29
  .#{$label}--bg {background-color: $color;}
42
30
  .#{$label} {color: $color;}
@@ -116,4 +104,40 @@
116
104
  .#{$label}-dark#{$i} {color: darken($color, $dark-increment * $i);}
117
105
  }
118
106
  }
107
+
108
+ .primary--bg {color: rgb(var(--w-base-bg-color-rgb));}
109
+ .white--bg {background-color: #fff;}
110
+ .white {color: #fff;}
111
+ .black--bg {background-color: #000;}
112
+ .black {color: #000;}
113
+ .transparent--bg {background-color: transparent;}
114
+ .transparent {color: transparent;}
115
+ .inherit--bg {background-color: inherit;}
116
+ .inherit {color: inherit;}
117
+ // ------------------------------------------------------
118
+
119
+ // Theming colors.
120
+ // These classes carry colors which change automatically when switching theme.
121
+ // ------------------------------------------------------
122
+ .base-color {color: rgba(var(--w-base-color-rgb), 0.7);}
123
+ .base-color--bg {background-color: rgb(var(--w-base-bg-color-rgb));}
124
+ .contrast-color {color: rgba(var(--w-contrast-color-rgb), 0.7);}
125
+ .contrast-color--bg {background-color: rgb(var(--w-contrast-bg-color-rgb));}
126
+ // ------------------------------------------------------
127
+
128
+ // Status colors - must stay last and have highest priority.
129
+ // ------------------------------------------------------
130
+ .info {color: var(--w-info-color);}
131
+ .info--bg {background-color: var(--w-info-color);color: #fff;}
132
+ .warning {color: var(--w-warning-color);}
133
+ .warning--bg {background-color: var(--w-warning-color);color: #fff;}
134
+ .success {color: var(--w-success-color);}
135
+ .success--bg {background-color: var(--w-success-color);color: #fff;}
136
+ .error {color: var(--w-error-color);}
137
+ .error--bg {background-color: var(--w-error-color);color: #fff;}
138
+ // ------------------------------------------------------
139
+
140
+ // The only colors remaining to define are user custom colors and shades.
141
+ // The associated CSS will be generated from dynamic-css.js, and injected as a first stylesheet,
142
+ // before this one (so the rules in this file have more priority).
119
143
  }
@@ -1,10 +1,3 @@
1
- // The CSS variables are used in the dynamic-css.js file in order to reuse the same SCSS
2
- // variable presets.
3
- :root {
4
- --base-increment: #{$base-increment};
5
- --css-scope: #{$css-scope};
6
- }
7
-
8
1
  // All these CSS classes will not be generated if the $use-layout-classes is set to false.
9
2
  @if $use-layout-classes {
10
3
  #{$css-scope} {
@@ -170,7 +163,7 @@
170
163
 
171
164
  // Sizes.
172
165
  // ----------------------------------------------
173
- // In all the sizes bellow, round(x / 2) * 2 to always have even numbers.
166
+ // In all the sizes below, round(x / 2) * 2 to always have even numbers.
174
167
  // Different heights with a mix of odd and even numbers will misalign
175
168
  // when vertically centering (vertical-align or align-items center).
176
169
  .size--xs {font-size: round(0.85 * $base-font-size);}
@@ -186,25 +179,25 @@
186
179
  // (https://www.w3.org/TR/css-variables-1/#using-variables),
187
180
  // the grid system is done dynamically in dynamic-css.js.
188
181
 
189
- // @media screen and (min-width: var(--breakpoint-xs)) {
182
+ // @media screen and (min-width: var(--w-breakpoint-xs)) {
190
183
  // @for $i from 0 through $grid-base {
191
184
  // .sm#{$grid-base - $i} {width: ($grid-base - $i) * 100% / $grid-base;}
192
185
  // }
193
186
  // }
194
187
 
195
- // @media screen and (min-width: var(--breakpoint-sm)) {
188
+ // @media screen and (min-width: var(--w-breakpoint-sm)) {
196
189
  // @for $i from 0 through $grid-base {
197
190
  // .md#{$grid-base - $i} {width: ($grid-base - $i) * 100% / $grid-base;}
198
191
  // }
199
192
  // }
200
193
 
201
- // @media screen and (min-width: var(--breakpoint-md)) {
194
+ // @media screen and (min-width: var(--w-breakpoint-md)) {
202
195
  // @for $i from 0 through $grid-base {
203
196
  // .lg#{$grid-base - $i} {width: ($grid-base - $i) * 100% / $grid-base;}
204
197
  // }
205
198
  // }
206
199
 
207
- // @media screen and (min-width: var(--breakpoint-lg)) {
200
+ // @media screen and (min-width: var(--w-breakpoint-lg)) {
208
201
  // @for $i from 0 through $grid-base {
209
202
  // .xl#{$grid-base - $i} {width: ($grid-base - $i) * 100% / $grid-base;}
210
203
  // }
@@ -1,3 +1,27 @@
1
+ @use "sass:map";
2
+
3
+ // This allows each UI component to be used in dark or light theme regardless of the global theme.
4
+ @mixin themeable {
5
+ // Will force the light style on this component.
6
+ &--light {
7
+ --w-base-bg-color-rgb: #{map.get($theme-light, 'base-bg-color-rgb')};
8
+ --w-base-color-rgb: #{map.get($theme-light, 'base-color-rgb')};
9
+ --w-contrast-bg-color-rgb: #{map.get($theme-light, 'contrast-bg-color-rgb')};
10
+ --w-contrast-color-rgb: #{map.get($theme-light, 'contrast-color-rgb')};
11
+ --w-disabled-color-rgb: #{map.get($theme-light, 'disabled-color-rgb')};
12
+ color: rgba(var(--w-base-color-rgb), 0.7);
13
+ }
14
+ // Will force the dark style on this component.
15
+ &--dark {
16
+ --w-base-bg-color-rgb: #{map.get($theme-dark, 'base-bg-color-rgb')};
17
+ --w-base-color-rgb: #{map.get($theme-dark, 'base-color-rgb')};
18
+ --w-contrast-bg-color-rgb: #{map.get($theme-dark, 'contrast-bg-color-rgb')};
19
+ --w-contrast-color-rgb: #{map.get($theme-dark, 'contrast-color-rgb')};
20
+ --w-disabled-color-rgb: #{map.get($theme-dark, 'disabled-color-rgb')};
21
+ color: rgba(var(--w-base-color-rgb), 0.7);
22
+ }
23
+ }
24
+
1
25
  @mixin default-transition($duration: $transition-duration, $delay: 0s) {
2
26
  transition: $duration $delay ease-in-out;
3
27
  }