From cdba38d0041ebbd15d348904b459a7d5e4af7efe Mon Sep 17 00:00:00 2001 From: Ross Gerbasi Date: Thu, 8 Feb 2018 08:17:23 -0600 Subject: [PATCH] feat(themes): theme builder app updates * theme-builder wip * Theme Builder updates - new CSS variable support - realtime color updating with alpha support (rgb generation) - auto tint/shade/contrast generation - auto step generation - CSS variable highlighting (forward and backwards) - Colourlovers Palette search (via local proxy) --- packages/core/package.json | 3 +- packages/core/scripts/theme-builder/server.js | 46 ++- .../scripts/theme-builder/src/components.d.ts | 70 ++-- .../theme-builder/src/components/Color.ts | 216 ++++++++++++ .../src/components/app-preview/app-preview.js | 56 ++++ .../components/app-preview/app-preview.tsx | 85 ++++- .../color-selector/color-selector.tsx | 78 ----- .../src/components/css-text/css-text.js | 79 +++++ .../src/components/css-text/css-text.tsx | 27 +- .../demo-selection/demo-selection.js | 47 +++ .../theme-builder/src/components/helpers.ts | 48 +-- .../components/theme-builder/theme-builder.js | 88 +++++ .../theme-builder/theme-builder.tsx | 22 +- .../theme-selector/theme-selector.css | 144 ++++++++ .../theme-selector/theme-selector.js | 130 ++++++++ .../theme-selector/theme-selector.tsx | 270 ++++++++++++--- .../variable-selector.css} | 7 +- .../variable-selector/variable-selector.tsx | 98 ++++++ .../core/scripts/theme-builder/src/index.html | 2 - .../theme-builder/src/theme-variables.js | 310 ++++++++++++++++++ .../theme-builder/src/theme-variables.ts | 231 +++++++++---- .../scripts/theme-builder/stencil.config.js | 2 +- .../src/components/alert/alert.ios.vars.scss | 2 +- .../src/components/button/button.ios.scss | 7 + .../components/button/button.ios.vars.scss | 3 + .../core/src/components/button/button.md.scss | 5 + .../src/components/button/button.md.vars.scss | 3 + .../core/src/components/button/button.scss | 1 - packages/core/src/themes/css/default.css | 121 +++---- 29 files changed, 1811 insertions(+), 390 deletions(-) create mode 100644 packages/core/scripts/theme-builder/src/components/Color.ts create mode 100644 packages/core/scripts/theme-builder/src/components/app-preview/app-preview.js delete mode 100644 packages/core/scripts/theme-builder/src/components/color-selector/color-selector.tsx create mode 100644 packages/core/scripts/theme-builder/src/components/css-text/css-text.js create mode 100644 packages/core/scripts/theme-builder/src/components/demo-selection/demo-selection.js create mode 100644 packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.js create mode 100644 packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.js rename packages/core/scripts/theme-builder/src/components/{color-selector/color-selector.css => variable-selector/variable-selector.css} (82%) create mode 100644 packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.tsx create mode 100644 packages/core/scripts/theme-builder/src/theme-variables.js diff --git a/packages/core/package.json b/packages/core/package.json index d6880687a9..70116a2357 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,7 +40,8 @@ "test.watch": "jest --watch --no-cache", "build-test-cmp": "stencil build --dev --config scripts/test-components/stencil.config.js", "theme-app-build": "stencil build --dev --config scripts/theme-builder/stencil.config.js", - "theme-builder": "npm run theme-app-build && sd concurrent \"stencil build --dev --watch\" \"stencil-dev-server\" \"npm run theme-server\" ", + "theme-builder": "sd concurrent \"npm run theme-app-build\" \"stencil build --dev --watch\" \"stencil-dev-server\" \"npm run theme-server\" ", + "theme-builder:dev": "sd concurrent \"npm run theme-app-build -- --watch\" \"stencil build --dev --watch\" \"stencil-dev-server\" \"npm run theme-server\" ", "theme-server": "node scripts/theme-builder/server.js", "tslint": "tslint --project .", "tslint-fix": "tslint --project . --fix", diff --git a/packages/core/scripts/theme-builder/server.js b/packages/core/scripts/theme-builder/server.js index 200cb3c820..4c477c6aef 100644 --- a/packages/core/scripts/theme-builder/server.js +++ b/packages/core/scripts/theme-builder/server.js @@ -12,7 +12,7 @@ const srcComponentsDir = path.join(__dirname, '../../', componentsPath); const srcCssDir = path.join(__dirname, '../../', cssPath); -function requestHandler(request, response) { +function requestHandler (request, response) { const parsedUrl = url.parse(request.url, true); response.setHeader('Access-Control-Allow-Origin', '*'); @@ -20,7 +20,8 @@ function requestHandler(request, response) { if (parsedUrl.pathname === '/data') { requestDataHandler(response); - + } else if (parsedUrl.pathname === '/color') { + requestColorHandler(request, response); } else if (parsedUrl.pathname === '/save-css') { requestSaveCssHandler(parsedUrl, response); @@ -33,7 +34,7 @@ function requestHandler(request, response) { } -function requestDataHandler(response) { +function requestDataHandler (response) { try { const demoPaths = glob.sync('**/index.html', { cwd: srcComponentsDir @@ -42,10 +43,10 @@ function requestDataHandler(response) { const demos = demoPaths.map(demo => { return { name: demo.toLowerCase() - .replace(/\\/g, ' ') - .replace(/\//g, ' ') - .replace(/ test/g, '') - .replace(/ index.html/g, ''), + .replace(/\\/g, ' ') + .replace(/\//g, ' ') + .replace(/ test/g, '') + .replace(/ index.html/g, ''), url: componentsPath + demo.replace(/\\/g, '/') }; }).sort((a, b) => { @@ -82,8 +83,31 @@ function requestDataHandler(response) { } } +function requestColorHandler (req, res) { + res.setHeader('Content-Type', 'application/json'); + const params = {} = req.url.split('?').filter((e,i) => i > 0)[0].split('&').map(e => {let [name, value]=e.split('='); return {[name]:value}})[0], + url = `http://www.colourlovers.com/api/palettes?format=json&keywords=${params.search}`; -function requestSaveCssHandler(parsedUrl, response) { + // console.log (`Proxy: ${url}`, params); + http.get(url, (resp) => { + let data = ''; + + resp.on('data', (chunk) => { + data += chunk; + }); + + // The whole response has been received. Print out the result. + resp.on('end', () => { + res.end(data); + }); + + }).on("error", (err) => { + console.log("Error: " + err.message); + res.end('{success: false}'); + }); +} + +function requestSaveCssHandler (parsedUrl, response) { try { const theme = (parsedUrl.query.theme || '').toLowerCase().trim(); if (!theme) { @@ -94,7 +118,7 @@ function requestSaveCssHandler(parsedUrl, response) { const filePath = path.join(srcCssDir, theme + '.css'); const css = parsedUrl.query.css || ''; - fs.writeFileSync(filePath, css, { encoding: 'utf8' }); + fs.writeFileSync(filePath, css, {encoding: 'utf8'}); console.log('css saved!', filePath); @@ -107,7 +131,7 @@ function requestSaveCssHandler(parsedUrl, response) { } -function requestDeleteCssHandler(parsedUrl, response) { +function requestDeleteCssHandler (parsedUrl, response) { try { const theme = (parsedUrl.query.theme || '').toLowerCase().trim(); if (!theme) { @@ -138,4 +162,4 @@ server.listen(port, (err) => { } console.log(`theme server: http://localhost:${port}/`); -}); \ No newline at end of file +}); diff --git a/packages/core/scripts/theme-builder/src/components.d.ts b/packages/core/scripts/theme-builder/src/components.d.ts index e626c51c34..7604e5227d 100644 --- a/packages/core/scripts/theme-builder/src/components.d.ts +++ b/packages/core/scripts/theme-builder/src/components.d.ts @@ -4,6 +4,9 @@ * and imports for stencil collections that might be configured in your stencil.config.js file */ +import { + Color, +} from './components/Color'; import { AppPreview as AppPreview @@ -32,38 +35,7 @@ declare global { cssText?: string; demoMode?: string; demoUrl?: string; - } - } -} - - -import { - ColorSelector as ColorSelector -} from './components/color-selector/color-selector'; - -declare global { - interface HTMLColorSelectorElement extends ColorSelector, HTMLElement { - } - var HTMLColorSelectorElement: { - prototype: HTMLColorSelectorElement; - new (): HTMLColorSelectorElement; - }; - interface HTMLElementTagNameMap { - "color-selector": HTMLColorSelectorElement; - } - interface ElementTagNameMap { - "color-selector": HTMLColorSelectorElement; - } - namespace JSX { - interface IntrinsicElements { - "color-selector": JSXElements.ColorSelectorAttributes; - } - } - namespace JSXElements { - export interface ColorSelectorAttributes extends HTMLAttributes { - isRgb?: boolean; - property?: string; - value?: string; + hoverProperty?: string; } } } @@ -186,9 +158,43 @@ declare global { } namespace JSXElements { export interface ThemeSelectorAttributes extends HTMLAttributes { + propertiesUsed?: string[]; themeData?: { name: string }[]; } } } + +import { + VariableSelector as VariableSelector +} from './components/variable-selector/variable-selector'; + +declare global { + interface HTMLVariableSelectorElement extends VariableSelector, HTMLElement { + } + var HTMLVariableSelectorElement: { + prototype: HTMLVariableSelectorElement; + new (): HTMLVariableSelectorElement; + }; + interface HTMLElementTagNameMap { + "variable-selector": HTMLVariableSelectorElement; + } + interface ElementTagNameMap { + "variable-selector": HTMLVariableSelectorElement; + } + namespace JSX { + interface IntrinsicElements { + "variable-selector": JSXElements.VariableSelectorAttributes; + } + } + namespace JSXElements { + export interface VariableSelectorAttributes extends HTMLAttributes { + isRgb?: boolean; + property?: string; + type?: 'color' | 'percent'; + value?: Color | string | number; + } + } +} + declare global { namespace JSX { interface StencilJSX {} } } diff --git a/packages/core/scripts/theme-builder/src/components/Color.ts b/packages/core/scripts/theme-builder/src/components/Color.ts new file mode 100644 index 0000000000..a01af5b247 --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/Color.ts @@ -0,0 +1,216 @@ +interface RGB { + r: number, + g: number, + b: number +} + +interface HSL { + h: number, + s: number, + l: number +} + + +export declare interface ColorStep { + id: string, + color: Color +} + +export declare interface ColorStepDefinition { + color?: Color, + increments: number[] +} + +function componentToHex (c) { + const hex = c.toString(16); + return hex.length == 1 ? `0${hex}` : hex; +} + +function expandHex (hex: string): string { + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (_m, r, g, b) { + return r + r + g + g + b + b; + }); + + return `#${hex.replace('#', '')}`; +} + +function hexToRGB (hex: string): RGB { + hex = expandHex(hex); + hex = hex.replace('#', ''); + const intValue: number = parseInt(hex, 16); + + return { + r: (intValue >> 16) & 255, + g: (intValue >> 8) & 255, + b: intValue & 255 + }; +} + +function hslToRGB ({h, s, l}: HSL): RGB { + h = h / 360; + s = s / 100; + l = l / 100; + if (s == 0) { + return { + r: l, + g: l, + b: l + }; + } + + const hue2rgb = function hue2rgb (p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }, + q = l < 0.5 ? l * (1 + s) : l + s - l * s, + p = 2 * l - q, + r = hue2rgb(p, q, h + (1 / 3)), + g = hue2rgb(p, q, h), + b = hue2rgb(p, q, h - (1 / 3)); + + return { + r: Math.round(r * 255), + g: Math.round(g * 255), + b: Math.round(b * 255) + }; +} + +function mixColors (color: Color, mixColor: Color, weight: number = .5): RGB { + const colorRGB: RGB = color.rgb, + mixColorRGB: RGB = mixColor.rgb, + mixColorWeight = 1 - weight; + + return { + r: Math.round(weight * mixColorRGB.r + mixColorWeight * colorRGB.r), + g: Math.round(weight * mixColorRGB.g + mixColorWeight * colorRGB.g), + b: Math.round(weight * mixColorRGB.b + mixColorWeight * colorRGB.b) + }; +} + +function rgbToHex ({r, g, b}: RGB) { + return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b); +} + +function rgbToHSL ({r, g, b}: RGB): HSL { + r = Math.max(Math.min(r / 255, 1), 0); + g = Math.max(Math.min(g / 255, 1), 0); + b = Math.max(Math.min(b / 255, 1), 0); + const max = Math.max(r, g, b), + min = Math.min(r, g, b), + l = (max + min) / 2; + let d, h, s; + + if (max !== min) { + d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + if (max === r) { + h = (g - b) / d + (g < b ? 6 : 0); + } else if (max === g) { + h = (b - r) / d + 2; + } else { + h = (r - g) / d + 4; + } + h = h / 6; + } else { + h = s = 0; + } + return { + h: Math.round(h * 360), + s: Math.round(s * 100), + l: Math.round(l * 100) + }; +} + +function rgbToYIQ ({r, g, b}: RGB): number { + return ((r * 299) + (g * 587) + (b * 114)) / 1000; +} + +export class Color { + readonly hex: string; + readonly rgb: RGB; + readonly hsl: HSL; + readonly yiq: number; + + public static isColor (value: string): Boolean { + if (/rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)/.test(value)) return true; + + return /(^#[0-9a-fA-F]+)/.test(value.trim()); + } + + constructor (value: string | RGB | HSL) { + if (typeof(value) === 'string' && /rgb\(/.test(value)) { + const matches = /rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)/.exec(value); + value = {r: parseInt(matches[0]), g: parseInt(matches[1]), b: parseInt(matches[2])}; + } else if (typeof(value) === 'string' && /hsl\(/.test(value)) { + const matches = /hsl\((\d{1,3}), ?(\d{1,3}%), ?(\d{1,3}%)\)/.exec(value); + value = {h: parseInt(matches[0]), s: parseInt(matches[1]), l: parseInt(matches[2])}; + } + + + if (typeof(value) === 'string') { + value = value.replace(/\s/g, ''); + this.hex = expandHex(value); + this.rgb = hexToRGB(this.hex); + this.hsl = rgbToHSL(this.rgb); + } else if ('r' in value && 'g' in value && 'b' in value) { + this.rgb = value; + this.hex = rgbToHex(this.rgb); + this.hsl = rgbToHSL(this.rgb); + } else if ('h' in value && 's' in value && 'l' in value) { + this.hsl = value; + this.rgb = hslToRGB(this.hsl); + this.hex = rgbToHex(this.rgb); + } else { + return null; + } + + this.yiq = rgbToYIQ(this.rgb); + } + + contrast (threshold: number = 128): Color { + return new Color((this.yiq >= threshold ? '#000' : '#fff')); + } + + shiftLightness (percent: number): Color { + const hsl: HSL = Object.assign({}, this.hsl, { + l: this.hsl.l * percent + }); + + return new Color(hsl); + } + + tint (percent: number = .1): Color { + percent = 1 + percent; + return this.shiftLightness(percent); + } + + shade (percent: number = .1): Color { + percent = 1 - percent; + return this.shiftLightness(percent); + } + + steps (from: ColorStepDefinition = {increments: [.2, .3, .5, .75]}): ColorStep[] { + const steps: ColorStep[] = [], + mixColor: Color = from.color || new Color((this.yiq > 128 ? '#000' : '#fff')); + + for (let i = 1; i <= from.increments.length; i++) { + const {r, g, b} = mixColors(this, mixColor, from.increments[i - 1]); + steps.push({ + id: (i * 100).toString(), + color: new Color({r,g,b}) + }); + } + + return steps; + } + + toList (): string { + const {r, g, b}: RGB = this.rgb; + return `${r},${g},${b}`; + } +} diff --git a/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.js b/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.js new file mode 100644 index 0000000000..0b0b18f018 --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.js @@ -0,0 +1,56 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +import { Component, Prop, Watch } from '@stencil/core'; +let AppPreview = class AppPreview { + onCssTextChange() { + console.log('AppPreview onCssTextChange'); + this.applyStyles(); + } + applyStyles() { + if (this.iframe && this.iframe.contentDocument && this.iframe.contentDocument.documentElement) { + const iframeDoc = this.iframe.contentDocument; + const themerStyleId = 'themer-style'; + let themerStyle = iframeDoc.getElementById(themerStyleId); + if (!themerStyle) { + themerStyle = iframeDoc.createElement('style'); + themerStyle.id = themerStyleId; + iframeDoc.documentElement.appendChild(themerStyle); + } + themerStyle.innerHTML = this.cssText; + } + } + onIframeLoad() { + this.applyStyles(); + } + render() { + const url = `${this.demoUrl}?ionicplatform=${this.demoMode}`; + return [ + h("div", null, + h("iframe", { src: url, ref: el => this.iframe = el, onLoad: this.onIframeLoad.bind(this) })) + ]; + } +}; +__decorate([ + Prop() +], AppPreview.prototype, "demoUrl", void 0); +__decorate([ + Prop() +], AppPreview.prototype, "demoMode", void 0); +__decorate([ + Prop() +], AppPreview.prototype, "cssText", void 0); +__decorate([ + Watch('cssText') +], AppPreview.prototype, "onCssTextChange", null); +AppPreview = __decorate([ + Component({ + tag: 'app-preview', + styleUrl: 'app-preview.css', + shadow: true + }) +], AppPreview); +export { AppPreview }; diff --git a/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.tsx b/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.tsx index 6050a7f1ea..3e50b0e5bd 100644 --- a/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.tsx +++ b/packages/core/scripts/theme-builder/src/components/app-preview/app-preview.tsx @@ -1,4 +1,5 @@ -import { Component, Prop, Watch } from '@stencil/core'; +import { Component, Event, EventEmitter, Listen, Prop, Watch } from '@stencil/core'; +import { Color } from '../Color'; @Component({ @@ -11,16 +12,36 @@ export class AppPreview { @Prop() demoUrl: string; @Prop() demoMode: string; @Prop() cssText: string; + @Prop() hoverProperty: string; + @Event() propertiesUsed: EventEmitter; + iframe: HTMLIFrameElement; + hasIframeListener: boolean = false; @Watch('cssText') - onCssTextChange() { + onCssTextChange () { console.log('AppPreview onCssTextChange'); this.applyStyles(); } - applyStyles() { + @Watch('hoverProperty') + onHoverPropertyChange () { + const el = this.iframe.contentDocument.documentElement; + el.style.cssText = ''; + if (this.hoverProperty) { + const computed = window.getComputedStyle(el), + value = computed.getPropertyValue(this.hoverProperty); + + if (Color.isColor(value)) { + el.style.setProperty(this.hoverProperty, '#ff0000'); + } else { + el.style.setProperty(this.hoverProperty, parseFloat(value) > .5 ? '.1': '1'); + } + } + } + + applyStyles () { if (this.iframe && this.iframe.contentDocument && this.iframe.contentDocument.documentElement) { const iframeDoc = this.iframe.contentDocument; const themerStyleId = 'themer-style'; @@ -30,17 +51,69 @@ export class AppPreview { themerStyle = iframeDoc.createElement('style'); themerStyle.id = themerStyleId; iframeDoc.documentElement.appendChild(themerStyle); + + const applicationStyle = iframeDoc.createElement('style'); + iframeDoc.documentElement.appendChild(applicationStyle); + applicationStyle.innerHTML = 'html.theme-property-searching body * { pointer-events: auto !important}' } themerStyle.innerHTML = this.cssText; } } - onIframeLoad() { - this.applyStyles(); + onIframeMouseMove (ev) { + if (ev.ctrlKey) { + const el: HTMLElement = this.iframe.contentDocument.documentElement; + + if (!el.classList.contains('theme-property-searching')) { + el.classList.add('theme-property-searching'); + } + const sheets = (this.iframe.contentDocument.styleSheets), + items: Element[] = Array.from(ev.currentTarget.querySelectorAll(':hover')), + properties = []; + + items.forEach(item => { + for (let i in sheets) { + const sheet: CSSStyleSheet = sheets[i] as CSSStyleSheet, + rules = sheet.rules || sheet.cssRules; + for (let r in rules) { + const rule: CSSStyleRule = rules[r] as CSSStyleRule; + if (item.matches(rule.selectorText)) { + const matches = rule.cssText.match(/(--ion.+?),/mgi); + if (matches) { + properties.push(...matches.map(match => match.replace(',', ''))); + } + } + } + } + }); + + this.propertiesUsed.emit({ + properties: Array.from(new Set(properties)).filter(prop => !/(-ios-|-md-)/.test(prop)) + }) + } } - render() { + @Listen('body:keyup', {capture: true}) + onKeyUp (ev: KeyboardEvent) { + if (!ev.ctrlKey) { + const el: HTMLElement = this.iframe.contentDocument.documentElement; + el.classList.remove('theme-property-searching'); + + this.propertiesUsed.emit({ + properties: [] + }) + } + } + + + onIframeLoad () { + this.applyStyles(); + + this.iframe.contentDocument.documentElement.addEventListener('mousemove', this.onIframeMouseMove.bind(this)); + } + + render () { const url = `${this.demoUrl}?ionicplatform=${this.demoMode}`; return [ diff --git a/packages/core/scripts/theme-builder/src/components/color-selector/color-selector.tsx b/packages/core/scripts/theme-builder/src/components/color-selector/color-selector.tsx deleted file mode 100644 index 4e2f9b5b89..0000000000 --- a/packages/core/scripts/theme-builder/src/components/color-selector/color-selector.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Component, Event, EventEmitter, Prop } from '@stencil/core'; -import { isValidColorValue } from '../helpers'; - - -@Component({ - tag: 'color-selector', - styleUrl: 'color-selector.css', - shadow: true -}) -export class ColorSelector { - - @Prop() property: string; - @Prop({ mutable: true }) value: string; - @Prop() isRgb: boolean; - isValid: boolean; - - onChange(ev) { - if (this.isRgb) { - this.value = hexToRgb(ev.currentTarget.value); - } else { - this.value = ev.currentTarget.value; - } - - this.colorChange.emit({ - property: this.property, - value: this.value - }); - } - - @Event() colorChange: EventEmitter; - - render() { - const value = this.value.trim().toLowerCase(); - const hex = rgbToHex(value); - - return [ -
-
- -
-
- -
-
- {this.property} -
-
- ]; - } -} - -function rgbToHex(value: string) { - if (value.indexOf('rgb') === -1) { - return value; - } - - var c = value.replace(/[\sa-z\(\);]+/gi, '').split(','); - c = c.map(s => parseInt(s, 10).toString(16).replace(/^([a-z\d])$/i, '0$1')); - - return '#' + c[0] + c[1] + c[2]; -} - -function hexToRgb(c: any) { - if (c.indexOf('#') === -1) { - return c; - } - c = c.replace(/#/, ''); - c = c.length % 6 ? c.replace(/(.)(.)(.)/, '$1$1$2$2$3$3') : c; - c = parseInt(c, 16); - - var a = parseFloat(a) || null; - - const r = (c >> 16) & 255; - const g = (c >> 8) & 255; - const b = (c >> 0) & 255; - - return `rgb${a ? 'a' : ''}(${[r, g, b, a].join().replace(/,$/, '')})`; -} diff --git a/packages/core/scripts/theme-builder/src/components/css-text/css-text.js b/packages/core/scripts/theme-builder/src/components/css-text/css-text.js new file mode 100644 index 0000000000..6f4a8e5ccb --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/css-text/css-text.js @@ -0,0 +1,79 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +import { Component, Prop } from '@stencil/core'; +import { STORED_THEME_KEY, deleteCssUrl, getThemeUrl, saveCssUrl } from '../helpers'; +let CssText = class CssText { + submitUpdate(ev) { + ev.stopPropagation(); + ev.preventDefault(); + this.saveCss(this.themeName, this.cssText); + } + saveCss(themeName, cssText) { + const url = saveCssUrl(themeName, cssText); + fetch(url).then(rsp => { + return rsp.text().then(txt => { + console.log('theme server response:', txt); + }); + }).catch(err => { + console.log(err); + }); + } + createNew(ev) { + ev.stopPropagation(); + ev.preventDefault(); + const name = prompt(`New theme name:`); + if (name) { + const themeName = name.split('.')[0].trim().toLowerCase(); + if (themeName.length) { + console.log('createNew themeName', themeName); + localStorage.setItem(STORED_THEME_KEY, themeName); + this.saveCss(themeName, this.cssText); + } + } + } + deleteTheme(ev) { + ev.stopPropagation(); + ev.preventDefault(); + const shouldDelete = confirm(`Sure you want to delete "${this.themeName}"?`); + if (shouldDelete) { + const url = deleteCssUrl(this.themeName); + fetch(url).then(rsp => { + return rsp.text().then(txt => { + console.log('theme server response:', txt); + }); + }).catch(err => { + console.log(err); + }); + localStorage.removeItem(STORED_THEME_KEY); + } + } + render() { + return [ + h("h1", null, getThemeUrl(this.themeName)), + h("div", null, + h("textarea", { readOnly: true, spellcheck: 'false' }, this.cssText)), + h("div", null, + h("button", { type: 'button', onClick: this.submitUpdate.bind(this) }, "Save Theme"), + h("button", { type: 'button', onClick: this.createNew.bind(this) }, "Create"), + h("button", { type: 'button', onClick: this.deleteTheme.bind(this) }, "Delete")) + ]; + } +}; +__decorate([ + Prop() +], CssText.prototype, "themeName", void 0); +__decorate([ + Prop() +], CssText.prototype, "cssText", void 0); +CssText = __decorate([ + Component({ + tag: 'css-text', + styleUrl: 'css-text.css', + shadow: true + }) +], CssText); +export { CssText }; diff --git a/packages/core/scripts/theme-builder/src/components/css-text/css-text.tsx b/packages/core/scripts/theme-builder/src/components/css-text/css-text.tsx index 467a29ab83..abc8cd1aa1 100644 --- a/packages/core/scripts/theme-builder/src/components/css-text/css-text.tsx +++ b/packages/core/scripts/theme-builder/src/components/css-text/css-text.tsx @@ -1,25 +1,25 @@ -import { Component, Prop } from '@stencil/core'; -import { STORED_THEME_KEY, deleteCssUrl, getThemeUrl, saveCssUrl } from '../helpers'; +import { Component, Element, Prop } from '@stencil/core'; +import { deleteCssUrl, getThemeUrl, saveCssUrl, STORED_THEME_KEY } from '../helpers'; @Component({ tag: 'css-text', - styleUrl: 'css-text.css', - shadow: true + styleUrl: 'css-text.css' }) export class CssText { + @Element() el: HTMLElement; @Prop() themeName: string; @Prop() cssText: string; - submitUpdate(ev: UIEvent) { + submitUpdate (ev: UIEvent) { ev.stopPropagation(); ev.preventDefault(); this.saveCss(this.themeName, this.cssText); } - saveCss(themeName: string, cssText: string) { + saveCss (themeName: string, cssText: string) { const url = saveCssUrl(themeName, cssText); fetch(url).then(rsp => { @@ -31,7 +31,7 @@ export class CssText { }); } - createNew(ev: UIEvent) { + createNew (ev: UIEvent) { ev.stopPropagation(); ev.preventDefault(); @@ -49,7 +49,7 @@ export class CssText { } } - deleteTheme(ev: UIEvent) { + deleteTheme (ev: UIEvent) { ev.stopPropagation(); ev.preventDefault(); @@ -69,18 +69,19 @@ export class CssText { } } - render() { + render () { + return [

{getThemeUrl(this.themeName)}

,
- +
,
- - - + + +
]; } diff --git a/packages/core/scripts/theme-builder/src/components/demo-selection/demo-selection.js b/packages/core/scripts/theme-builder/src/components/demo-selection/demo-selection.js new file mode 100644 index 0000000000..7b2a465d13 --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/demo-selection/demo-selection.js @@ -0,0 +1,47 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +import { Component, Event, Prop } from '@stencil/core'; +let DemoSelection = class DemoSelection { + onChangeUrl(ev) { + this.demoUrlChange.emit(ev.currentTarget.value); + } + onChangeMode(ev) { + this.demoModeChange.emit(ev.currentTarget.value); + } + render() { + return [ + h("div", null, + h("select", { onChange: this.onChangeUrl.bind(this) }, this.demoData.map(d => h("option", { value: d.url, selected: d.url === this.demoUrl }, d.name))), + h("select", { onChange: this.onChangeMode.bind(this) }, + h("option", { value: 'md', selected: 'md' === this.demoMode }, "md"), + h("option", { value: 'ios', selected: 'ios' === this.demoMode }, "ios"))) + ]; + } +}; +__decorate([ + Prop() +], DemoSelection.prototype, "demoData", void 0); +__decorate([ + Prop() +], DemoSelection.prototype, "demoUrl", void 0); +__decorate([ + Prop() +], DemoSelection.prototype, "demoMode", void 0); +__decorate([ + Event() +], DemoSelection.prototype, "demoUrlChange", void 0); +__decorate([ + Event() +], DemoSelection.prototype, "demoModeChange", void 0); +DemoSelection = __decorate([ + Component({ + tag: 'demo-selection', + styleUrl: 'demo-selection.css', + shadow: true + }) +], DemoSelection); +export { DemoSelection }; diff --git a/packages/core/scripts/theme-builder/src/components/helpers.ts b/packages/core/scripts/theme-builder/src/components/helpers.ts index 6a926d44bb..afcaf76caf 100644 --- a/packages/core/scripts/theme-builder/src/components/helpers.ts +++ b/packages/core/scripts/theme-builder/src/components/helpers.ts @@ -1,6 +1,7 @@ export const SERVER_DOMAIN = `http://localhost:5454`; export const DATA_URL = `${SERVER_DOMAIN}/data`; +export const COLOR_URL = `${SERVER_DOMAIN}/color`; export const SAVE_CSS_URL = `${SERVER_DOMAIN}/save-css`; export const DELETE_CSS_URL = `${SERVER_DOMAIN}/delete-css`; export const CSS_THEME_FILE_PATH = `/src/themes/css`; @@ -21,50 +22,3 @@ export function getThemeUrl(themeName: string) { export const STORED_DEMO_URL_KEY = 'theme-builder-demo-url'; export const STORED_DEMO_MODE_KEY = 'theme-builder-demo-mode'; export const STORED_THEME_KEY = 'theme-builder-theme-url'; - - -export function cleanCssValue(value: string) { - if (typeof value === 'string') { - value = (value || '').trim().toLowerCase().replace(/ /g, ''); - - if (value.length) { - if (value.charAt(0) === '#') { - return cleanHexValue(value); - } - - if (value.charAt(0) === 'r') { - return cleanRgb(value); - } - } - } - - return ''; -} - -function cleanHexValue(value: string) { - return '#' + value.substr(1).split('').map(c => { - return /[a-f]|[0-9]/.test(c) ? c : ''; - }).join('').substr(0, 6); -} - - -function cleanRgb(value: string) { - return value.split('').map(c => { - return /[rgba0-9\,\.\(\)]/.test(c) ? c : ''; - }).join(''); -} - -export function isValidColorValue(value: string) { - if (value) { - if (value.charAt(0) === '#') { - const rxValidHex = /^#[0-9a-f]{6}$/i; - return rxValidHex.test(value); - } - if (value.charAt(0) === 'r') { - const rxValidRgb = /([R][G][B][A]?[(]\s*([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(\s*,\s*((0\.[0-9]{1})|(1\.0)|(1)))?[)])/i; - return rxValidRgb.test(value); - } - } - - return false; -} diff --git a/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.js b/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.js new file mode 100644 index 0000000000..ffb7029a05 --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.js @@ -0,0 +1,88 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +import { Component, Listen, State } from '@stencil/core'; +import { DATA_URL, STORED_DEMO_MODE_KEY, STORED_DEMO_URL_KEY } from '../helpers'; +let ThemeBuilder = class ThemeBuilder { + constructor() { + this.cssText = ''; + this.themeName = ''; + } + componentWillLoad() { + return fetch(DATA_URL).then(rsp => { + return rsp.json().then(data => { + this.demoData = data.demos; + this.themeData = data.themes; + this.initUrl(); + }); + }).catch(err => { + console.log('ThemeBuilder componentWillLoad', err); + }); + } + initUrl() { + console.log('ThemeBuilder initUrl'); + const storedUrl = localStorage.getItem(STORED_DEMO_URL_KEY); + const defaultUrl = this.demoData[0].url; + this.demoUrl = storedUrl || defaultUrl; + const storedMode = localStorage.getItem(STORED_DEMO_MODE_KEY); + const defaultMode = 'md'; + this.demoMode = storedMode || defaultMode; + } + onDemoUrlChange(ev) { + this.demoUrl = ev.detail; + localStorage.setItem(STORED_DEMO_URL_KEY, this.demoUrl); + } + onDemoModeChange(ev) { + this.demoMode = ev.detail; + localStorage.setItem(STORED_DEMO_MODE_KEY, this.demoMode); + } + onThemeCssChange(ev) { + this.cssText = ev.detail.cssText; + this.themeName = ev.detail.themeName; + console.log('ThemeBuilder themeCssChange', this.themeName); + } + render() { + return [ + h("main", null, + h("section", { class: 'preview-column' }, + h("demo-selection", { demoData: this.demoData, demoUrl: this.demoUrl, demoMode: this.demoMode }), + h("app-preview", { demoUrl: this.demoUrl, demoMode: this.demoMode, cssText: this.cssText })), + h("section", { class: 'selector-column' }, + h("theme-selector", { themeData: this.themeData })), + h("section", null, + h("css-text", { themeName: this.themeName, cssText: this.cssText }))) + ]; + } +}; +__decorate([ + State() +], ThemeBuilder.prototype, "demoUrl", void 0); +__decorate([ + State() +], ThemeBuilder.prototype, "demoMode", void 0); +__decorate([ + State() +], ThemeBuilder.prototype, "cssText", void 0); +__decorate([ + State() +], ThemeBuilder.prototype, "themeName", void 0); +__decorate([ + Listen('demoUrlChange') +], ThemeBuilder.prototype, "onDemoUrlChange", null); +__decorate([ + Listen('demoModeChange') +], ThemeBuilder.prototype, "onDemoModeChange", null); +__decorate([ + Listen('themeCssChange') +], ThemeBuilder.prototype, "onThemeCssChange", null); +ThemeBuilder = __decorate([ + Component({ + tag: 'theme-builder', + styleUrl: 'theme-builder.css', + shadow: true + }) +], ThemeBuilder); +export { ThemeBuilder }; diff --git a/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.tsx b/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.tsx index 01a27e0e09..5ef9f3fe1d 100644 --- a/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.tsx +++ b/packages/core/scripts/theme-builder/src/components/theme-builder/theme-builder.tsx @@ -15,6 +15,8 @@ export class ThemeBuilder { @State() demoUrl: string; @State() demoMode: string; @State() cssText: string = ''; + @State() hoverProperty: string; + @State() propertiesUsed: string[]; @State() themeName: string = ''; componentWillLoad() { @@ -60,17 +62,31 @@ export class ThemeBuilder { console.log('ThemeBuilder themeCssChange', this.themeName); } + @Listen('propertyHoverStart') + onPropertyHoverStart(ev) { + this.hoverProperty = ev.detail.property; + } + + @Listen('propertyHoverStop') + onPropertyHoverStop() { + this.hoverProperty = undefined; + } + + @Listen('propertiesUsed') + onPropertiesUsed(ev) { + this.propertiesUsed = ev.detail.properties; + } + render() { return [
-
- +
- +
diff --git a/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.css b/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.css index 2cd1eff771..357b0e0d92 100644 --- a/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.css +++ b/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.css @@ -6,3 +6,147 @@ select { section { margin: 10px; } + +.palettes { + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.palette { + position: relative; + display: flex; + flex-direction: row; + width: 100%; + height: 75px; + padding: 8px 0; +} + +.palette:after { + content: attr(data-title); + position: absolute; + pointer-events: none; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + text-align: center; + justify-content: center; + align-items: center; + font-size: 28px; + opacity: .2; +} + +.palette .color { + position: relative; + flex: 1; + padding: 0 8px; +} + +.color:after { + position: absolute; + top: 0; + content: "#" attr(data-color); + pointer-events: none; + font-size: 12px; + text-shadow: rgba(255, 255, 255, .5) 1px 1px; +} + +.color-buttons { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-content: center; + display: none; +} + +.color:hover .color-buttons { + display: flex; +} + +.color-buttons button { + outline: none; + border: none; + font-size: 12px; + margin: 1px; + padding: 4px; + cursor: pointer; + opacity: .5; + text-shadow: rgba(255, 255, 255, .5) 1px 1px; +} + +.color-buttons button.primary { + background-color: var(--ion-color-primary); +} + +.color-buttons button.secondary { + background-color: var(--ion-color-secondary); +} + +.color-buttons button.tertiary { + background-color: var(--ion-color-tertiary); +} + +.color-buttons button.success { + background-color: var(--ion-color-success); +} + +.color-buttons button.warning { + background-color: var(--ion-color-warning); +} + +.color-buttons button.danger { + background-color: var(--ion-color-danger); +} + +.color-buttons button.light { + background-color: var(--ion-color-light); +} + +.color-buttons button.medium { + background-color: var(--ion-color-medium); +} + +.color-buttons button.dark { + background-color: var(--ion-color-dark); +} + +.color-buttons button.background { + background-color: var(--ion-background-color); +} + +.color-buttons button.text { + background-color: var(--ion-text-color); +} + +.color-buttons button:hover { + background-color: rgba(161, 60, 68, 0.53); +} + +.top-bar { + display: flex; + padding: 8px; + flex-direction: row; + justify-content: space-between; +} + +.search-button { + margin-left: 6px; +} + +.search-toggle, .search-button { + background-color: rgb(89, 124, 155); + border: none; + color: white; + padding: 4px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 11px; +} diff --git a/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.js b/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.js new file mode 100644 index 0000000000..5b6d975343 --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.js @@ -0,0 +1,130 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +import { Component, Event, Listen, Prop, State } from '@stencil/core'; +import { THEME_VARIABLES } from '../../theme-variables'; +import * as Helpers from '../helpers'; +console.log(Helpers); +let ThemeSelector = class ThemeSelector { + constructor() { + this.themeVariables = []; + } + onChangeUrl(ev) { + this.themeName = ev.currentTarget.value; + localStorage.setItem(Helpers.STORED_THEME_KEY, this.themeName); + this.loadThemeCss(); + } + componentWillLoad() { + const storedThemeName = localStorage.getItem(Helpers.STORED_THEME_KEY); + const defaultThemeName = this.themeData[0].name; + this.themeName = storedThemeName || defaultThemeName; + this.loadThemeCss(); + } + loadThemeCss() { + console.log('ThemeSelector loadThemeCss'); + const themeUrl = Helpers.getThemeUrl(this.themeName); + return fetch(themeUrl).then(rsp => { + return rsp.text().then(css => { + this.parseCss(css); + this.generateCss(); + }); + }); + } + parseCss(css) { + console.log('ThemeSelector parseCss'); + const themer = document.getElementById('themer'); + themer.innerHTML = css; + const computed = window.getComputedStyle(document.body); + this.themeVariables = THEME_VARIABLES.map(themeVariable => { + const value = (computed.getPropertyValue(themeVariable.property) || PLACEHOLDER_COLOR); + return { + property: themeVariable.property.trim(), + value: value, + type: themeVariable.type, + computed: themeVariable.computed, + isRgb: value.indexOf('rgb') > -1 + }; + }); + } + generateCss() { + console.log('ThemeSelector generateCss', this.themeName); + const c = []; + c.push(`/** ${this.themeName} theme **/`); + c.push(`\n`); + c.push(':root {'); + this.themeVariables.forEach(themeVariable => { + themeVariable.value = Helpers.cleanCssValue(themeVariable.value); + c.push(` ${themeVariable.property}: ${themeVariable.value};`); + }); + c.push('}'); + const cssText = c.join('\n'); + this.themeCssChange.emit({ + cssText: cssText, + themeName: this.themeName + }); + } + onColorChange(ev) { + console.log('ThemeSelector colorChange'); + this.themeVariables = this.themeVariables.map(themeVariable => { + let value = themeVariable.value; + if (ev.detail.property === themeVariable.property) { + value = ev.detail.value; + } + return { + property: themeVariable.property, + value: value, + type: themeVariable.type, + computed: themeVariable.computed, + isRgb: themeVariable.isRgb + }; + }); + this.themeVariables + .filter(themeVariable => !!themeVariable.computed) + .forEach(themeVariable => { + const computed = themeVariable.computed || {}, fn = computed.fn, params = computed.params; + if (Helpers[fn]) { + themeVariable.value = Helpers[fn].apply(fn, params); + } + else { + console.log(`Unknown Helpers Function '${fn}'`); + } + }); + this.generateCss(); + } + render() { + return [ + h("div", null, + h("select", { onChange: this.onChangeUrl.bind(this) }, this.themeData.map(d => h("option", { value: d.name, selected: this.themeName === d.name }, d.name))), + h("section", null, this.themeVariables + .filter(d => !d.computed) + .map(d => h("variable-selector", { property: d.property, value: d.value, isRgb: d.isRgb, type: d.type })))) + ]; + } +}; +__decorate([ + State() +], ThemeSelector.prototype, "themeName", void 0); +__decorate([ + State() +], ThemeSelector.prototype, "themeVariables", void 0); +__decorate([ + Prop() +], ThemeSelector.prototype, "themeData", void 0); +__decorate([ + Event() +], ThemeSelector.prototype, "themeCssChange", void 0); +__decorate([ + Listen('colorChange') +], ThemeSelector.prototype, "onColorChange", null); +ThemeSelector = __decorate([ + Component({ + tag: 'theme-selector', + styleUrl: 'theme-selector.css', + shadow: true + }) +], ThemeSelector); +export { ThemeSelector }; +const PLACEHOLDER_COLOR = `#ff00ff`; diff --git a/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.tsx b/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.tsx index dbc21cac7f..8f3b276d22 100644 --- a/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.tsx +++ b/packages/core/scripts/theme-builder/src/components/theme-selector/theme-selector.tsx @@ -1,51 +1,63 @@ -import { Component, Event, EventEmitter, Listen, Prop, State } from '@stencil/core'; -import { STORED_THEME_KEY, cleanCssValue, getThemeUrl } from '../helpers'; -import { THEME_VARIABLES } from '../../theme-variables'; +import { Component, Element, Event, EventEmitter, Listen, Prop, State } from '@stencil/core'; +import { THEME_VARIABLES } from '../../theme-variables'; +import { Color, ColorStep } from '../Color'; +import { COLOR_URL, getThemeUrl, STORED_THEME_KEY } from '../helpers'; +interface ThemeVariable { + property: string; + value?: Color | number | string; + computed?: string; +} + +const PLACEHOLDER_COLOR = '#ff00ff'; @Component({ tag: 'theme-selector', - styleUrl: 'theme-selector.css', - shadow: true + styleUrl: 'theme-selector.css' }) + export class ThemeSelector { + @Element() el: HTMLThemeSelectorElement; @State() themeName: string; - @State() themeVariables: { property: string; value?: string; isRgb?: boolean; }[] = []; + @State() themeVariables: ThemeVariable[] = []; + @State() searchMode: boolean; + @State() palettes: any[]; + @Prop() propertiesUsed: string[] = []; @Prop() themeData: { name: string }[]; @Event() themeCssChange: EventEmitter; + @Event() propertyHoverStart: EventEmitter; + @Event() propertyHoverStop: EventEmitter; + private currentHoveredProperty: string; - onChangeUrl(ev) { + async onChangeUrl (ev) { this.themeName = ev.currentTarget.value; localStorage.setItem(STORED_THEME_KEY, this.themeName); - this.loadThemeCss(); + await this.loadThemeCss(); } - componentWillLoad() { + async componentWillLoad () { const storedThemeName = localStorage.getItem(STORED_THEME_KEY); const defaultThemeName = this.themeData[0].name; this.themeName = storedThemeName || defaultThemeName; - this.loadThemeCss(); + await this.loadThemeCss(); } - loadThemeCss() { + async loadThemeCss () { console.log('ThemeSelector loadThemeCss'); const themeUrl = getThemeUrl(this.themeName); - return fetch(themeUrl).then(rsp => { - return rsp.text().then(css => { - this.parseCss(css); - this.generateCss(); - }); - }); + const css = await fetch(themeUrl).then(r => r.text()); + this.parseCss(css); + this.generateCss(); } - parseCss(css: string) { + parseCss (css: string) { console.log('ThemeSelector parseCss'); const themer = document.getElementById('themer') as HTMLStyleElement; @@ -54,16 +66,16 @@ export class ThemeSelector { const computed = window.getComputedStyle(document.body); this.themeVariables = THEME_VARIABLES.map(themeVariable => { - const value = (computed.getPropertyValue(themeVariable.property) || PLACEHOLDER_COLOR).trim().toLowerCase(); - return { + const value = (computed.getPropertyValue(themeVariable.property) || PLACEHOLDER_COLOR); + + return Object.assign({}, themeVariable, { property: themeVariable.property.trim(), - value: value, - isRgb: value.indexOf('rgb') > -1 - }; + value: themeVariable.computed ? value : (!Color.isColor(value) ? parseFloat(value) : new Color(value)) + }); }); } - generateCss() { + generateCss () { console.log('ThemeSelector generateCss', this.themeName); const c: string[] = []; @@ -72,8 +84,8 @@ export class ThemeSelector { c.push(':root {'); this.themeVariables.forEach(themeVariable => { - themeVariable.value = cleanCssValue(themeVariable.value); - c.push(` ${themeVariable.property}: ${themeVariable.value};`); + const value = themeVariable.value; + c.push(` ${themeVariable.property}: ${value instanceof Color ? value.hex : value};`); }); c.push('}'); @@ -85,40 +97,200 @@ export class ThemeSelector { }); } - @Listen('colorChange') - onColorChange(ev) { - console.log('ThemeSelector colorChange'); + hoverProperty () { + const targets: Element[] = Array.from(this.el.querySelectorAll(':hover')), + selector: Element = targets.find(target => { + return target.tagName.toLowerCase() === 'variable-selector'; + }); - this.themeVariables = this.themeVariables.map(themeVariable => { - let value = themeVariable.value; + if (selector) { + const property = (selector as HTMLVariableSelectorElement).property; - if (ev.detail.property === themeVariable.property) { - value = ev.detail.value; + if (this.currentHoveredProperty !== property) { + this.propertyHoverStop.emit({ + property: this.currentHoveredProperty + }); + + this.currentHoveredProperty = property; + this.propertyHoverStart.emit({ + property: this.currentHoveredProperty + }); } + } + } - return { - property: themeVariable.property, - value: value, - isRgb: themeVariable.isRgb - }; + @Listen('colorChange') + onColorChange (ev) { + console.log('ThemeSelector colorChange'); + this.changeColor(ev.detail.property, ev.detail.value); + } + + changeColor (property: string, value: Color | string) { + this.themeVariables = this.themeVariables.map(themeVariable => { + if (property === themeVariable.property) { + return Object.assign({}, themeVariable, { + value: value instanceof Color ? value : themeVariable.value instanceof Color ? new Color(value) : value + }); + } + return themeVariable; }); + this.themeVariables + .filter(themeVariable => !!themeVariable.computed) + .forEach(themeVariable => { + const computed = themeVariable.computed, + referenceVariable = this.themeVariables.find(themeVariable => themeVariable.property === computed), + value = referenceVariable.value; + if (value instanceof Color) { + themeVariable.value = value.toList(); + } + }); + this.generateCss(); } - render() { + @Listen('generateColors') + onGenerateColors (ev) { + const color: Color = ev.detail.color, + steps: Boolean = ev.detail.steps, + property = ev.detail.property; + + if (color && property) { + if (steps) { + const steps: ColorStep[] = color.steps(); + steps.forEach((step: ColorStep) => { + const themeVariable: ThemeVariable = this.themeVariables.find((variable: ThemeVariable) => variable.property === `${property}-step-${step.id}`); + themeVariable && (themeVariable.value = step.color); + }); + } else { + const tint: ThemeVariable = this.themeVariables.find((variable: ThemeVariable) => variable.property === `${property}-tint`), + shade: ThemeVariable = this.themeVariables.find((variable: ThemeVariable) => variable.property === `${property}-shade`), + contrast: ThemeVariable = this.themeVariables.find((variable: ThemeVariable) => variable.property === `${property}-contrast`); + + tint && (tint.value = color.tint()); + shade && (shade.value = color.shade()); + contrast && (contrast.value = color.contrast()); + } + + this.generateCss(); + //TODO: Figure out why we need this typed to any + (this.el as any).forceUpdate(); + } + } + + @Listen('body:keydown') + onKeyDown (ev: MouseEvent) { + if (ev.ctrlKey) { + this.hoverProperty(); + } + } + + @Listen('body:keyup') + onKeyUp (ev: KeyboardEvent) { + if (this.currentHoveredProperty && !ev.ctrlKey) { + this.propertyHoverStop.emit({ + property: this.currentHoveredProperty + }); + this.currentHoveredProperty = null; + } + } + + @Listen('mousemove') + onMouseMove (ev: MouseEvent) { + if (ev.ctrlKey) { + this.hoverProperty(); + } + } + + onSearchInput (ev: KeyboardEvent) { + if (ev.keyCode == 13) { + this.search(); + } + } + + toggleSearchMode () { + this.searchMode = !this.searchMode; + } + + async search () { + const input: HTMLInputElement = this.el.querySelector('#searchInput') as HTMLInputElement, + value = input.value; + + input.value = ''; + + try { + this.palettes = await fetch(`${COLOR_URL}?search=${value}&stuff=poop`).then(r => r.json()) || []; + } catch (e) { + this.palettes = []; + } + } + + onColorClick (ev: MouseEvent) { + console.log(ev); + let target: HTMLElement = ev.currentTarget as HTMLElement; + const property = target.getAttribute('data-property'); + + while (target && !target.classList.contains('color')) { + target = target.parentElement as HTMLElement; + } + + const color = target.getAttribute('data-color'); + this.changeColor(property, color); + } + + render () { + const + onColorClick = this.onColorClick.bind(this), + variables =
+ { + this.themeVariables + .filter(d => !d.computed) + .map(d => = 0 ? 'used' : ''} + property={d.property} value={d.value}>) + } +
, + search =
+
+ + +
+
+ { + (this.palettes || []).map((d: any) =>
+ {(d.colors || []).map((c: string) =>
+
+ + + + + + + + + + + +
+
)} +
) + } +
+ +
; + return [
- - -
- {this.themeVariables.map(d => )} -
+
+ + +
+ {this.searchMode ? search : variables}
]; } -} - -const PLACEHOLDER_COLOR = `#ff00ff`; +}; diff --git a/packages/core/scripts/theme-builder/src/components/color-selector/color-selector.css b/packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.css similarity index 82% rename from packages/core/scripts/theme-builder/src/components/color-selector/color-selector.css rename to packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.css index 1b3caf8c31..81dc3dfd74 100644 --- a/packages/core/scripts/theme-builder/src/components/color-selector/color-selector.css +++ b/packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.css @@ -10,6 +10,7 @@ section { .property-label { font-family: Courier New, Courier, monospace; white-space: nowrap; + cursor: pointer; flex: 1; padding-left: 10px; @@ -36,4 +37,8 @@ input[type="color"]::-webkit-color-swatch-wrapper { input[type="color"]::-webkit-color-swatch { border: 1px solid black; -} \ No newline at end of file +} + +:host(.used) section { + background-color: var(--variable-selector-color, rgba(255, 149, 243, 0.91)); +} diff --git a/packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.tsx b/packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.tsx new file mode 100644 index 0000000000..555530c421 --- /dev/null +++ b/packages/core/scripts/theme-builder/src/components/variable-selector/variable-selector.tsx @@ -0,0 +1,98 @@ +import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; +import { Color } from '../Color'; + + +@Component({ + tag: 'variable-selector', + styleUrl: 'variable-selector.css', + shadow: true +}) +export class VariableSelector { + + @Element() el: HTMLElement; + @Prop() property: string; + @Prop() type: 'color' | 'percent'; + @Prop({mutable: true}) value: Color | string | number; + @Prop() isRgb: boolean; + @Event() colorChange: EventEmitter; + @Event() generateColors: EventEmitter; + + @Method() + getProperty () { + return this.property; + } + + onChange (ev) { + const input: HTMLInputElement = ev.currentTarget, + value = ev.currentTarget.value; + + if (input.type === 'color') { + this.value = new Color(value); + } else if (input.type === 'text') { + if (Color.isColor(value)) { + this.value = new Color(value); + } else { + return; + } + } else if (input.type === 'range') { + this.value = value / 100; + } + + this.colorChange.emit({ + property: this.property, + value: this.value + }); + } + + + @Listen('dblclick') + onMouseUp (ev) { + if (ev.altKey) { + const color = this.value as Color; + if (/(primary|secondary|tertiary|success|warning|danger|light|medium|dark)$/.test(this.property)) { + this.generateColors.emit({ + color, + property: this.property + }); + } else if (/(^--ion-background-color$|^--ion-text-color$)/.test(this.property)) { + this.generateColors.emit({ + color, + steps: true, + property: this.property + }); + } + } + } + + render () { + if (this.value instanceof Color || this.value == null) { + const color = this.value && this.value as Color, + value = color.hex, {r, g, b} = color.rgb; + this.el.style.setProperty('--variable-selector-color', `rgba(${r}, ${g}, ${b}, .5`); + return [ +
+
+ +
+
+ +
+
+ {this.property} +
+
+ ]; + } + const value = parseFloat(this.value as string); + return [ +
+
+ +
+
+ {this.property} +
+
+ ]; + } +} diff --git a/packages/core/scripts/theme-builder/src/index.html b/packages/core/scripts/theme-builder/src/index.html index 0174f11381..fae310c616 100644 --- a/packages/core/scripts/theme-builder/src/index.html +++ b/packages/core/scripts/theme-builder/src/index.html @@ -12,8 +12,6 @@ - - diff --git a/packages/core/scripts/theme-builder/src/theme-variables.js b/packages/core/scripts/theme-builder/src/theme-variables.js new file mode 100644 index 0000000000..e170f22d4f --- /dev/null +++ b/packages/core/scripts/theme-builder/src/theme-variables.js @@ -0,0 +1,310 @@ +export const THEME_VARIABLES = [ + { + property: '--ion-alpha-lowest', + type: 'percent' + }, + { + property: '--ion-alpha-low', + type: 'percent' + }, + { + property: '--ion-alpha-medium', + type: 'percent' + }, + { + property: '--ion-alpha-high', + type: 'percent' + }, + { + property: '--ion-alpha-highest', + type: 'percent' + }, + { + property: '--ion-color-primary' + }, + { + property: '--ion-color-primary-contrast' + }, + { + property: '--ion-color-primary-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-primary' + ] + } + }, + { + property: '--ion-color-primary-shade' + }, + { + property: '--ion-color-primary-tint' + }, + { + property: '--ion-color-secondary' + }, + { + property: '--ion-color-secondary-contrast' + }, + { + property: '--ion-color-secondary-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-secondary' + ] + } + }, + { + property: '--ion-color-secondary-shade' + }, + { + property: '--ion-color-secondary-tint' + }, + { + property: '--ion-color-tertiary' + }, + { + property: '--ion-color-tertiary-contrast' + }, + { + property: '--ion-color-tertiary-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-tertiary' + ] + } + }, + { + property: '--ion-color-tertiary-shade' + }, + { + property: '--ion-color-tertiary-tint' + }, + { + property: '--ion-color-success' + }, + { + property: '--ion-color-success-contrast' + }, + { + property: '--ion-color-success-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-success' + ] + } + }, + { + property: '--ion-color-success-shade' + }, + { + property: '--ion-color-success-tint' + }, + { + property: '--ion-color-warning' + }, + { + property: '--ion-color-warning-contrast' + }, + { + property: '--ion-color-warning-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-warning' + ] + } + }, + { + property: '--ion-color-warning-shade' + }, + { + property: '--ion-color-warning-tint' + }, + { + property: '--ion-color-danger' + }, + { + property: '--ion-color-danger-contrast' + }, + { + property: '--ion-color-danger-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-danger' + ] + } + }, + { + property: '--ion-color-danger-shade' + }, + { + property: '--ion-color-danger-tint' + }, + { + property: '--ion-color-light' + }, + { + property: '--ion-color-light-contrast' + }, + { + property: '--ion-color-light-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-light' + ] + } + }, + { + property: '--ion-color-light-shade' + }, + { + property: '--ion-color-light-tint' + }, + { + property: '--ion-color-medium' + }, + { + property: '--ion-color-medium-contrast' + }, + { + property: '--ion-color-medium-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-medium' + ] + } + }, + { + property: '--ion-color-medium-shade' + }, + { + property: '--ion-color-medium-tint' + }, + { + property: '--ion-color-dark' + }, + { + property: '--ion-color-dark-contrast' + }, + { + property: '--ion-color-dark-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-color-dark' + ] + } + }, + { + property: '--ion-color-dark-shade' + }, + { + property: '--ion-color-dark-tint' + }, + { + property: '--ion-backdrop-color' + }, + { + property: '--ion-background-color' + }, + { + property: '--ion-background-color-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-background-color' + ] + } + }, + { + property: '--ion-background-color-step-100' + }, + { + property: '--ion-background-color-step-200' + }, + { + property: '--ion-background-color-step-300' + }, + { + property: '--ion-background-color-step-400' + }, + { + property: '--ion-border-color' + }, + { + property: '--ion-box-shadow-color' + }, + { + property: '--ion-text-color' + }, + { + property: '--ion-text-color-rgb', + computed: { + fn: 'colorToRGBList', + params: [ + '--ion-text-color' + ] + } + }, + { + property: '--ion-text-color-step-100' + }, + { + property: '--ion-text-color-step-200' + }, + { + property: '--ion-text-color-step-300' + }, + { + property: '--ion-text-color-step-400' + }, + { + property: '--ion-tabbar-background-color' + }, + { + property: '--ion-tabbar-border-color' + }, + { + property: '--ion-tabbar-text-color' + }, + { + property: '--ion-tabbar-text-color-active' + }, + { + property: '--ion-toolbar-background-color' + }, + { + property: '--ion-toolbar-border-color' + }, + { + property: '--ion-toolbar-color-active' + }, + { + property: '--ion-toolbar-color-inactive' + }, + { + property: '--ion-toolbar-text-color' + }, + { + property: '--ion-item-background-color' + }, + { + property: '--ion-item-background-color-active' + }, + { + property: '--ion-item-border-color' + }, + { + property: '--ion-item-text-color' + }, + { + property: '--ion-placeholder-text-color' + } +]; diff --git a/packages/core/scripts/theme-builder/src/theme-variables.ts b/packages/core/scripts/theme-builder/src/theme-variables.ts index d90c2abee9..f0a0818c0d 100644 --- a/packages/core/scripts/theme-builder/src/theme-variables.ts +++ b/packages/core/scripts/theme-builder/src/theme-variables.ts @@ -1,151 +1,250 @@ - -export const THEME_VARIABLES = [ +export const THEME_VARIABLES: {property: string, type?: 'percent' | 'color', computed?: string}[] = [ + { + property: '--ion-alpha-lowest' + }, + { + property: '--ion-alpha-low' + }, + { + property: '--ion-alpha-medium' + }, + { + property: '--ion-alpha-high' + }, + { + property: '--ion-alpha-highest' + }, { property: '--ion-color-primary' }, - { - property: '--ion-color-primary-tint' - }, - { - property: '--ion-color-primary-shade' - }, { property: '--ion-color-primary-contrast' }, + { + property: '--ion-color-primary-rgb', + computed: '--ion-color-primary' + }, + { + property: '--ion-color-primary-shade' + }, + { + property: '--ion-color-primary-tint' + }, { property: '--ion-color-secondary' }, - { - property: '--ion-color-secondary-tint' - }, - { - property: '--ion-color-secondary-shade' - }, { property: '--ion-color-secondary-contrast' }, + { + property: '--ion-color-secondary-rgb', + computed: '--ion-color-secondary' + }, + { + property: '--ion-color-secondary-shade' + }, + { + property: '--ion-color-secondary-tint' + }, { property: '--ion-color-tertiary' }, - { - property: '--ion-color-tertiary-tint' - }, - { - property: '--ion-color-tertiary-shade' - }, { property: '--ion-color-tertiary-contrast' }, + { + property: '--ion-color-tertiary-rgb', + computed: '--ion-color-tertiary' + }, + { + property: '--ion-color-tertiary-shade' + }, + { + property: '--ion-color-tertiary-tint' + }, { property: '--ion-color-success' }, - { - property: '--ion-color-success-tint' - }, - { - property: '--ion-color-success-shade' - }, { property: '--ion-color-success-contrast' }, + { + property: '--ion-color-success-rgb', + computed: '--ion-color-success' + }, + { + property: '--ion-color-success-shade' + }, + { + property: '--ion-color-success-tint' + }, { property: '--ion-color-warning' }, - { - property: '--ion-color-warning-tint' - }, - { - property: '--ion-color-warning-shade' - }, { property: '--ion-color-warning-contrast' }, + { + property: '--ion-color-warning-rgb', + computed: '--ion-color-warning' + }, + { + property: '--ion-color-warning-shade' + }, + { + property: '--ion-color-warning-tint' + }, { property: '--ion-color-danger' }, - { - property: '--ion-color-danger-tint' - }, - { - property: '--ion-color-danger-shade' - }, { property: '--ion-color-danger-contrast' }, + { + property: '--ion-color-danger-rgb', + computed: '--ion-color-danger' + }, + { + property: '--ion-color-danger-shade' + }, + { + property: '--ion-color-danger-tint' + }, { property: '--ion-color-light' }, - { - property: '--ion-color-light-tint' - }, - { - property: '--ion-color-light-shade' - }, { property: '--ion-color-light-contrast' }, + { + property: '--ion-color-light-rgb', + computed: '--ion-color-light' + }, + { + property: '--ion-color-light-shade' + }, + { + property: '--ion-color-light-tint' + }, { property: '--ion-color-medium' }, - { - property: '--ion-color-medium-tint' - }, - { - property: '--ion-color-medium-shade' - }, { property: '--ion-color-medium-contrast' }, + { + property: '--ion-color-medium-rgb', + computed: '--ion-color-medium' + }, + { + property: '--ion-color-medium-shade' + }, + { + property: '--ion-color-medium-tint' + }, { property: '--ion-color-dark' }, - { - property: '--ion-color-dark-tint' - }, - { - property: '--ion-color-dark-shade' - }, { property: '--ion-color-dark-contrast' }, { - property: '--ion-text-color' + property: '--ion-color-dark-rgb', + computed: '--ion-color-dark' }, { - property: '--ion-text-color-alt' + property: '--ion-color-dark-shade' + }, + { + property: '--ion-color-dark-tint' + }, + { + property: '--ion-backdrop-color' }, { property: '--ion-background-color' }, { - property: '--ion-background-color-alt' + property: '--ion-background-color-rgb', + computed: '--ion-background-color' }, { - property: '--ion-toolbar-background-color' + property: '--ion-background-color-step-100' + }, + { + property: '--ion-background-color-step-200' + }, + { + property: '--ion-background-color-step-300' + }, + { + property: '--ion-background-color-step-400' + }, + { + property: '--ion-border-color' + }, + { + property: '--ion-box-shadow-color' + }, + { + property: '--ion-text-color' + }, + { + property: '--ion-text-color-rgb', + computed: '--ion-text-color' + }, + { + property: '--ion-text-color-step-100' + }, + { + property: '--ion-text-color-step-200' + }, + { + property: '--ion-text-color-step-300' + }, + { + property: '--ion-text-color-step-400' }, { property: '--ion-tabbar-background-color' }, + { + property: '--ion-tabbar-border-color' + }, { property: '--ion-tabbar-text-color' }, { property: '--ion-tabbar-text-color-active' }, + { + property: '--ion-toolbar-background-color' + }, + { + property: '--ion-toolbar-border-color' + }, + { + property: '--ion-toolbar-color-active' + }, + { + property: '--ion-toolbar-color-inactive' + }, + { + property: '--ion-toolbar-text-color' + }, { property: '--ion-item-background-color' }, { - property: '--ion-item-background-color-alt' + property: '--ion-item-background-color-active' }, { - property: '--ion-border-color' + property: '--ion-item-border-color' }, { - property: '--ion-backdrop-color' + property: '--ion-item-text-color' }, { - property: '--ion-box-shadow-color' - }, - + property: '--ion-placeholder-text-color' + } ]; diff --git a/packages/core/scripts/theme-builder/stencil.config.js b/packages/core/scripts/theme-builder/stencil.config.js index f490bbfa14..2c127d3425 100644 --- a/packages/core/scripts/theme-builder/stencil.config.js +++ b/packages/core/scripts/theme-builder/stencil.config.js @@ -8,4 +8,4 @@ exports.devServer = { root: '../../', watchGlob: 'src/**', openUrl: '/theme-builder' -} +}; diff --git a/packages/core/src/components/alert/alert.ios.vars.scss b/packages/core/src/components/alert/alert.ios.vars.scss index 45152d1c00..53f772ab83 100644 --- a/packages/core/src/components/alert/alert.ios.vars.scss +++ b/packages/core/src/components/alert/alert.ios.vars.scss @@ -156,7 +156,7 @@ $alert-ios-button-border-width: $hairlines-width !default; $alert-ios-button-border-style: solid !default; /// @prop - Border color of the alert button -$alert-ios-button-border-color: $background-ios-color-step-100 !default; +$alert-ios-button-border-color: ion-color-alpha($text-ios-color-value, text-ios-color, $alpha-low) !default; /// @prop - Border radius of the alert button $alert-ios-button-border-radius: 0 !default; diff --git a/packages/core/src/components/button/button.ios.scss b/packages/core/src/components/button/button.ios.scss index 047832e75c..418dcaabfc 100644 --- a/packages/core/src/components/button/button.ios.scss +++ b/packages/core/src/components/button/button.ios.scss @@ -34,6 +34,13 @@ opacity: $button-ios-opacity-hover; } +a[disabled], +button[disabled], +.button[disabled] { + opacity: $button-ios-opacity-disabled; +} + + // iOS Default Button Color Mixin // -------------------------------------------------- diff --git a/packages/core/src/components/button/button.ios.vars.scss b/packages/core/src/components/button/button.ios.vars.scss index 7a25c13591..61be82a95e 100644 --- a/packages/core/src/components/button/button.ios.vars.scss +++ b/packages/core/src/components/button/button.ios.vars.scss @@ -57,6 +57,9 @@ $button-ios-opacity-hover: .8 !default; /// @prop - Background color of the focused button $button-ios-background-color-focused: ion-color($colors-ios, $button-ios-background-color, shade, ios) !default; +/// @prop - Opacity of the button when disabled +$button-ios-opacity-disabled: $alpha-ios-medium !default; + // iOS Large Button // -------------------------------------------------- diff --git a/packages/core/src/components/button/button.md.scss b/packages/core/src/components/button/button.md.scss index 8bdada729d..8ed6644560 100644 --- a/packages/core/src/components/button/button.md.scss +++ b/packages/core/src/components/button/button.md.scss @@ -46,6 +46,11 @@ background-color: $button-md-text-color; } +a[disabled], +button[disabled], +.button[disabled] { + opacity: $button-md-opacity-disabled; +} // Material Design Default Button Color Mixin // -------------------------------------------------- diff --git a/packages/core/src/components/button/button.md.vars.scss b/packages/core/src/components/button/button.md.vars.scss index c6d30a7f95..51d7e88526 100644 --- a/packages/core/src/components/button/button.md.vars.scss +++ b/packages/core/src/components/button/button.md.vars.scss @@ -78,6 +78,9 @@ $button-md-ripple-background-color: $text-md-color-step-200 !default; /// @prop - Background color of the focused button $button-md-background-color-focused: ion-color($colors-md, $button-md-background-color, shade, md) !default; +/// @prop - Opacity of the button when disabled +$button-md-opacity-disabled: $alpha-md-medium !default; + // Material Design Large Button // -------------------------------------------------- diff --git a/packages/core/src/components/button/button.scss b/packages/core/src/components/button/button.scss index db9ba77e4a..8b1569b6bb 100644 --- a/packages/core/src/components/button/button.scss +++ b/packages/core/src/components/button/button.scss @@ -57,7 +57,6 @@ a[disabled], button[disabled], .button[disabled] { cursor: default; - opacity: .4; pointer-events: none; } diff --git a/packages/core/src/themes/css/default.css b/packages/core/src/themes/css/default.css index 4b8553eb89..40e649ae2b 100644 --- a/packages/core/src/themes/css/default.css +++ b/packages/core/src/themes/css/default.css @@ -1,119 +1,84 @@ /** default theme **/ + :root { - --ion-alpha-lowest: .06; - --ion-alpha-low: .1; - --ion-alpha-medium: .4; - --ion-alpha-high: .75; - --ion-alpha-highest: .9; + --ion-alpha-lowest: 0.06; + --ion-alpha-low: 0.1; + --ion-alpha-medium: 0.4; + --ion-alpha-high: 0.75; + --ion-alpha-highest: 0.9; --ion-color-primary: #488aff; - --ion-color-primary-contrast: #fff; - --ion-color-primary-rgb: '72,138,255'; + --ion-color-primary-contrast: #ffffff; + --ion-color-primary-rgb: "72,138,255"; --ion-color-primary-shade: #3f79e0; --ion-color-primary-tint: #427feb; --ion-color-secondary: #32db64; - --ion-color-secondary-contrast: #fff; - --ion-color-secondary-rgb: '50,219,100'; + --ion-color-secondary-contrast: #ffffff; + --ion-color-secondary-rgb: "50,219,100"; --ion-color-secondary-shade: #2cc158; --ion-color-secondary-tint: #2ec95c; --ion-color-tertiary: #f4a942; - --ion-color-tertiary-contrast: #fff; - --ion-color-tertiary-rgb: '244,169,66'; + --ion-color-tertiary-contrast: #ffffff; + --ion-color-tertiary-rgb: "244,169,66"; --ion-color-tertiary-shade: #d6903d; --ion-color-tertiary-tint: #ffa529; --ion-color-success: #10dc60; - --ion-color-success-contrast: #fff; - --ion-color-success-rgb: '16,220,96'; + --ion-color-success-contrast: #ffffff; + --ion-color-success-rgb: "16,220,96"; --ion-color-success-shade: #10cb60; --ion-color-success-tint: #23df6d; --ion-color-warning: #ffce00; - --ion-color-warning-contrast: #000; - --ion-color-warning-rgb: '255,206,0'; + --ion-color-warning-contrast: #000000; + --ion-color-warning-rgb: "255,206,0"; --ion-color-warning-shade: #f1c100; --ion-color-warning-tint: #ffd214; --ion-color-danger: #f53d3d; - --ion-color-danger-contrast: #fff; - --ion-color-danger-rgb: '245,61,61'; + --ion-color-danger-contrast: #ffffff; + --ion-color-danger-rgb: "245,61,61"; --ion-color-danger-shade: #d83636; --ion-color-danger-tint: #e13838; --ion-color-light: #f4f4f4; - --ion-color-light-contrast: #000; - --ion-color-light-rgb: '244,244,244'; + --ion-color-light-contrast: #000000; + --ion-color-light-rgb: "244,244,244"; --ion-color-light-shade: #d7d7d7; --ion-color-light-tint: #e0e0e0; --ion-color-medium: #989aa2; - --ion-color-medium-contrast: #000; - --ion-color-medium-rgb: '152,154,162'; + --ion-color-medium-contrast: #000000; + --ion-color-medium-rgb: "152,154,162"; --ion-color-medium-shade: #8c8e95; --ion-color-medium-tint: #86888f; - --ion-color-dark: #222; - --ion-color-dark-contrast: #fff; - --ion-color-dark-rgb: '34,34,34'; + --ion-color-dark: #222222; + --ion-color-dark-contrast: #ffffff; + --ion-color-dark-rgb: "34,34,34"; --ion-color-dark-shade: #343434; --ion-color-dark-tint: #3d3d3d; - --ion-backdrop-color: #000; - --ion-background-color: #fff; - --ion-background-color-rgb: '255,255,255'; + --ion-backdrop-color: #000000; + --ion-background-color: #ffffff; + --ion-background-color-rgb: "255,255,255"; --ion-background-color-step-100: #f2f2f2; --ion-background-color-step-200: #dcdcdc; --ion-background-color-step-300: #bdbdbd; - --ion-background-color-step-400: #444; + --ion-background-color-step-400: #444444; --ion-border-color: #b2b2b2; - --ion-box-shadow-color: #000; - --ion-text-color: #000; - --ion-text-color-rgb: '0,0,0'; - --ion-text-color-step-100: #222; - --ion-text-color-step-200: #666; - --ion-text-color-step-300: #999; + --ion-box-shadow-color: #000000; + --ion-text-color: #000000; + --ion-text-color-rgb: "0,0,0"; + --ion-text-color-step-100: #222222; + --ion-text-color-step-200: #666666; + --ion-text-color-step-300: #999999; --ion-text-color-step-400: #c5c5c5; --ion-tabbar-background-color: #f8f8f8; - --ion-tabbar-border-color: var(--ion-border-color); + --ion-tabbar-border-color: #b2b2b2; --ion-tabbar-text-color: #8c8c8c; --ion-tabbar-text-color-active: #488aff; --ion-toolbar-background-color: #f8f8f8; - --ion-toolbar-border-color: var(--ion-border-color); + --ion-toolbar-border-color: #b2b2b2; --ion-toolbar-color-active: #488aff; --ion-toolbar-color-inactive: #8c8c8c; - --ion-toolbar-text-color: var(--ion-text-color); - --ion-item-background-color: var(--ion-background-color); - --ion-item-background-color-active: var(--ion-background-color); + --ion-toolbar-text-color: #000000; + --ion-item-background-color: #ffffff; + --ion-item-background-color-active: #ffffff; --ion-item-border-color: #c8c7cc; - --ion-item-text-color: var(--ion-text-color); - --ion-placeholder-text-color: #999; - - /* Material Design */ - --ion-color-md-light: #e3e3e3; - --ion-color-md-light-contrast: #000; - --ion-color-md-light-rgb: '227,227,227'; - --ion-color-md-light-shade: #d0d0d0; - --ion-color-md-light-tint: #f0f0f0; - --ion-border-md-color: #c1c4cd; - --ion-text-md-color-step-200: var(--ion-text-color-step-200); - --ion-tabbar-md-border-color: rgba(0, 0, 0, .07); - --ion-tabbar-md-text-color: var(--ion-text-md-color-step-200); - --ion-toolbar-md-border-color: var(--ion-border-md-color); - --ion-toolbar-md-text-color: #424242; - --ion-item-md-background-color-active: #f1f1f1; - --ion-item-md-border-color: #dedede; - - /* iOS */ - --ion-color-ios-primary: var(--ion-color-primary); - --ion-background-ios-color: var(--ion-background-color); - --ion-background-ios-color-step-100: #f7f7f7; - --ion-background-ios-color-step-200: #bdbdbd; - --ion-background-ios-color-step-300: #999; - --ion-background-ios-color-step-400: #222; - --ion-text-ios-color-step-100: #222; - --ion-text-ios-color-step-200: #666; - --ion-text-ios-color-step-300: #8f8f8f; - --ion-text-ios-color-step-400: #aeaeae; - --ion-border-ios-color: #c1c4cd; - --ion-tabbar-ios-translucent-background-color: rgba(248, 248, 248, 0.8); - --ion-tabbar-ios-border-color: rgba(0, 0, 0, .2); - --ion-tabbar-ios-text-color-active: var(--ion-color-ios-primary); - --ion-toolbar-ios-translucent-background-color: rgba(248, 248, 248, 0.8); - --ion-toolbar-ios-border-color: rgba(0, 0, 0, .2); - --ion-toolbar-ios-color-active: var(--ion-color-ios-primary); - --ion-item-ios-background-color: var(--ion-background-ios-color); - --ion-item-ios-background-color-active: #d9d9d9; -} + --ion-item-text-color: #000000; + --ion-placeholder-text-color: #999999; +} \ No newline at end of file