UNPKG

wave-ui

Version:

A UI framework for Vue.js 3 (and 2) with only the bright side. :sunny:

309 lines (272 loc) 11.9 kB
// Defaults CSS variables. Will be overridden in the main function of this file (the export default) // which fetches the CSS :root variables generated by _layout.scss. const cssVars = { cssScope: '.w-app', baseIncrement: 4 } // Global var for faster results in the resize event handler. let breakpointsDef = { keys: [], values: [] } let currentBreakpoint = null // Generates the CSS for all the dynamic colors and shades. E.g. // :root {[color1-variable], [color2-variable]} // .color1--bg {background-color: [color1-variable]} // .color1 {color: [color1-variable]} const generateColors = (themeColors, generateShadeCssVariables) => { let styles = '' const cssVariables = {} // Extract status colors and place them after the other colors. const { info, warning, success, error, shades, ...colors } = themeColors const { cssScope } = cssVars // User custom colors. // ------------------------------------------------------ for (const colorName in colors) { styles += `${cssScope} .${colorName}--bg{background-color:var(--w-${colorName}-color)}` + `${cssScope} .${colorName}{color:var(--w-${colorName}-color)}` } // The shades don't need css vars. for (const colorName in shades) { styles += `${cssScope} .${colorName}--bg{background-color:${shades[colorName]}}` + `${cssScope} .${colorName}{color:${shades[colorName]}}` } // Creating CSS3 variables. // ------------------------------------------------------ // Create a CSS variable for each color for theming and reuse in components. // Status colors must remain after the other colors so they have priority in form validations. // That only makes sense when there are 2 colors on the same element: e.g. `span.primary.error`. const allColors = { ...colors, info, warning, success, error } for (const colorName in allColors) cssVariables[colorName] = allColors[colorName]?.color ?? allColors[colorName] if (generateShadeCssVariables) { for (const colorName in shades) cssVariables[colorName] = shades[colorName] } let cssVariablesString = '' Object.entries(cssVariables).forEach(([colorName, colorHex]) => { cssVariablesString += `--w-${colorName}-color: ${colorHex};` }) return `:root{${cssVariablesString}}${styles}` } // Generate the layout grid. E.g. xs1, xs2, ..., xl12. const generateBreakpoints = (breakpoints, grid) => { let styles = '' const { cssScope } = cssVars // Can't add dynamic breakpoints as CSS variables: // CSS variables are not supported in media queries yet. // https://www.w3.org/TR/css-variables-1/#using-variables // const cssVariables = [] // Object.entries(config.breakpoints).forEach(([label, value]) => { // cssVariables.push(`--breakpoint-${label}: ${value}px`) // }) // styles += `:root {${cssVariables.join(';')}}` // Define media queries for each breakpoint: xs, sm, md, lg, xl. breakpoints.forEach(({ min, label }) => { // Discard `xs` since the min is 0 (`media query (min-width: 0)`), and leave in _layout.scss. if (label === 'xs') { // For each breakpoint, loop from 1 to 12. E.g. xs1, xs2, ..., xl12. for (let i = 0; i < grid; i++) { styles += `${cssScope} .${label}${grid - i}{` + `width:${parseFloat(((grid - i) * 100 / grid).toFixed(4))}%;}` } } else { styles += `@media(min-width:${min}px){` // For each breakpoint, loop from 1 to 12. E.g. xs1, xs2, ..., xl12. for (let i = 0; i < grid; i++) { styles += `${cssScope} .${label}${grid - i}{` + `width:${parseFloat(((grid - i) * 100 / grid).toFixed(4))}%;}` } styles += '}' } }) return styles } /* THIS WILL NOT BE AN OPTION (UNLESS I CHANGE MY MIND): Having spaces classes per breakpoint is really an interesting feature, so that we can use something like: `smd-ma2 mdu-ma12`. The drawback is that it generates so much CSS that I don't think it is worth it. In this test, for only 2 margin rules, it already generates 35kb of CSS... const generateBreakpointSpaces = breakpoints => { let styles = '' const { cssScope, baseIncrement } = cssVars breakpoints.forEach(({ label, min }) => { // Discard `xs` since the min is 0 (`media query (min-width: 0)`), and leave in _layout.scss. // Discard `xl`: xlu is just like xl. if (!['xs', 'xl'].includes(label)) { styles += `@media(min-width:${min}px){` for (let i = -12; i < 25; i++) { styles += `${cssScope} .${label}u-ma${i}{margin:${i * baseIncrement}px}` styles += `${cssScope} .${label}u-mx${i}{margin-right:${i * baseIncrement}px;margin-left:${i * baseIncrement}px}` } styles += '}' } }) breakpoints.forEach(({ label, min, max }) => { styles += `@media (min-width:${min}px) and (max-width:${max}px){` for (let i = -12; i < 25; i++) { styles += `${cssScope} .${label}-ma${i}{margin:${i * baseIncrement}px}` styles += `${cssScope} .${label}-mx${i}{margin-right:${i * baseIncrement}px;margin-left:${i * baseIncrement}px}` } styles += '}' }) breakpoints.forEach(({ label, max }) => { // Discard `xs`: xsd is just like xs. // Discard `xl`: xld is just like the basic rules already in CSS. if (!['xs', 'xl'].includes(label)) { styles += `@media (max-width:${max}px){` for (let i = -12; i < 25; i++) { styles += `${cssScope} .${label}d-ma${i}{margin:${i * baseIncrement}px}` styles += `${cssScope} .${label}d-mx${i}{margin-right:${i * baseIncrement}px;margin-left:${i * baseIncrement}px}` } styles += '}' } }) // console.log(styles) // debugger return styles } */ const genBreakpointLayoutClasses = breakpoints => { let styles = '' const { cssScope, baseIncrement } = cssVars const layoutClasses = [ 'show{display:block}', 'hide{display:none}', 'd-flex{display:flex}', 'd-iflex{display:inline-flex}', 'd-block{display:block}', 'd-iblock{display:inline-block}', 'text-left{text-align:left}', 'text-center{text-align:center}', 'text-right{text-align:right}', 'text-nowrap{white-space:nowrap}', 'row{flex-direction:row}', 'column{flex-direction:column}', 'column-reverse{flex-direction:column-reverse}', 'grow{flex-grow:1;flex-basis:auto}', 'no-grow{flex-grow:0}', 'shrink{flex-shrink:1;margin-left:auto;margin-right:auto}', 'no-shrink{flex-shrink:0}', 'wrap{flex-wrap: wrap}', 'no-wrap{flex-wrap: nowrap}', 'fill-width{width:100%}', 'fill-height{height:100%}', 'basis-zero{flex-basis:0}', 'align-start{align-items:flex-start}', 'align-center{align-items:center}', 'align-end{align-items:flex-end}', 'align-self-start{align-self:flex-start}', 'align-self-center{align-self:center}', 'align-self-end{align-self:flex-end}', 'align-self-stretch{align-self:stretch}', 'justify-start{justify-content:flex-start}', 'justify-center{justify-content:center}', 'justify-end{justify-content:flex-end}', 'justify-space-between{justify-content:space-between}', 'justify-space-around{justify-content:space-around}', 'justify-space-evenly{justify-content:space-evenly}' ] const array12 = Array(12).fill() // Define media queries for each breakpoint: xs, sm, md, lg, xl. breakpoints.forEach(({ label, min }) => { // Discard `xs` since the min is 0 (`media query (min-width: 0)`), and leave in _layout.scss. if (label !== 'xs') { styles += `@media(min-width:${min}px){` + layoutClasses.map(rule => `${cssScope} .${label}u-${rule}`).join('') + // w-grid columns and gap. array12.map((item, i) => `.w-grid.${label}u-columns${i + 1}{grid-template-columns:repeat(${i + 1},1fr);}`).join('') + array12.map((item, i) => `.w-flex.${label}u-gap${i + 1},.w-grid.${label}u-gap${i + 1}{gap:${(i + 1) * baseIncrement}px;}`).join('') + `.w-flex.${label}u-gap0,.w-flex.${label}u-gap0{gap:0}` + '}' } }) breakpoints.forEach(({ label, min, max }) => { styles += `@media (min-width:${min}px) and (max-width:${max}px){` + layoutClasses.map(rule => `${cssScope} .${label}-${rule}`).join('') + // w-grid columns and gap. array12.map((item, i) => `.w-grid.${label}-columns${i + 1}{grid-template-columns:repeat(${i + 1},1fr);}`).join('') + array12.map((item, i) => `.w-flex.${label}-gap${i + 1},.w-grid.${label}-gap${i + 1}{gap:${(i + 1) * baseIncrement}px;}`).join('') + `.w-flex.${label}-gap0,.w-flex.${label}-gap0{gap:0}` + '}' }) breakpoints.forEach(({ label, max }) => { if (label !== 'xl') { styles += `@media (max-width:${max}px){` + layoutClasses.map(rule => `${cssScope} .${label}d-${rule}`).join('') + // w-grid columns and gap. array12.map((item, i) => `.w-grid.${label}d-columns${i + 1}{grid-template-columns:repeat(${i + 1},1fr);}`).join('') + array12.map((item, i) => `.w-flex.${label}d-gap${i + 1},.w-grid.${label}d-gap${i + 1}{gap:${(i + 1) * baseIncrement}px;}`).join('') + `.w-flex.${label}d-gap0,.w-flex.${label}d-gap0{gap:0}` + '}' } }) return styles } const getBreakpoint = $waveui => { const width = window.innerWidth const breakpoints = breakpointsDef.values.slice(0) // Most performant lookup. breakpoints.push(width) breakpoints.sort((a, b) => a - b) const breakpoint = breakpointsDef.keys[breakpoints.indexOf(width)] || 'xl' if (breakpoint !== currentBreakpoint) { currentBreakpoint = breakpoint $waveui.breakpoint = { name: breakpoint, xs: breakpoint === 'xs', sm: breakpoint === 'sm', md: breakpoint === 'md', lg: breakpoint === 'lg', xl: breakpoint === 'xl', width } } $waveui.breakpoint.width = window.innerWidth } // Should run on first mounted hook. export const injectCSSInDOM = $waveui => { const { config } = $waveui breakpointsDef = { keys: Object.keys(config.breakpoints), values: Object.values(config.breakpoints) } // Inject global dynamic CSS classes in document head. if (!document.getElementById('wave-ui-styles')) { const css = document.createElement('style') css.id = 'wave-ui-styles' css.innerHTML = doDynamicCSS(config) const firstStyle = document.head.querySelectorAll('style,link[rel="stylesheet"]')[0] if (firstStyle) firstStyle.before(css) else document.head.appendChild(css) } getBreakpoint($waveui) window.addEventListener('resize', () => getBreakpoint($waveui)) } export const injectColorsCSSInDOM = (themeColors, colorPalette, generateShadeCssVariables) => { // Inject global dynamic CSS classes in document head. if (!document.getElementById('wave-ui-colors')) { const css = document.createElement('style') css.id = 'wave-ui-colors' css.innerHTML = generateColors(themeColors, colorPalette, generateShadeCssVariables) const firstStyle = document.head.querySelectorAll('style,link[rel="stylesheet"]')[0] if (firstStyle) firstStyle.before(css) else document.head.appendChild(css) } } const doDynamicCSS = config => { const entries = Object.entries(config.breakpoints) const breakpointsDef = entries.map(([label, max], i) => { // Construct the breakpoint objects. const [, value = 0] = entries[i - 1] || [] return { label, min: value ? value + 1 : 0, max } }) const computedStyles = getComputedStyle(document.documentElement) cssVars.cssScope = computedStyles.getPropertyValue('--w-css-scope') cssVars.baseIncrement = parseInt(computedStyles.getPropertyValue('--w-base-increment')) let styles = '' styles += generateBreakpoints(breakpointsDef, config.css.grid) if (config.css.breakpointLayoutClasses) styles += genBreakpointLayoutClasses(breakpointsDef) // if (config.css.breakpointSpaces) styles += generateBreakpointSpaces(breakpointsDef) return styles }