Skip to content

Commit

Permalink
feat: Theme component with app theme. (#9418)
Browse files Browse the repository at this point in the history
Added the feature that you can theme components
with the app theme by creating a css file in
'frontend/theme/myTheme/components' with the
component tag name e.g. 'my-field'  should have 'my-field.css'

Fixes #9348
# Conflicts:
#	flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponent.java
  • Loading branch information
caalador committed Mar 2, 2021
1 parent d7f1156 commit b9079f4
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
],
"repository": "vaadin/flow",
"name": "@vaadin/application-theme-plugin",
"version": "0.1.3",
"version": "0.1.4",
"main": "application-theme-plugin.js",
"author": "Vaadin Ltd",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
const glob = require('glob');
const path = require('path');

// Special folder inside a theme for component themes that go inside the component shadow root
const themeComponentsFolder = 'components';
// The contents of a global CSS file with this name in a theme is always added to
// the document. E.g. @font-face must be in this
const themeFileAlwaysAddToDocument = 'document.css';
Expand Down Expand Up @@ -48,13 +50,22 @@ function generateThemeFile(themeFolder, themeName) {
cwd: themeFolder,
nodir: true,
});
const componentsFiles = glob.sync('*.css', {
cwd: path.resolve(themeFolder, themeComponentsFolder),
nodir: true,
});

let themeFile = headerImport;

if(componentsFiles.length > 0){
themeFile += 'import { css, unsafeCSS, registerStyles } from \'@vaadin/vaadin-themable-mixin/register-styles\';';
}

themeFile += injectGlobalCssMethod;

const imports = [];
const globalCssCode = [];
const componentCssCode = [];

globalFiles.forEach((global) => {
const filename = path.basename(global);
Expand All @@ -66,17 +77,41 @@ function generateThemeFile(themeFolder, themeName) {
globalCssCode.push(`injectGlobalCss(${variable}.toString(), target);\n`);
});

componentsFiles.forEach((componentCss) => {
const filename = path.basename(componentCss);
const tag = filename.replace('.css', '');
const variable = camelCase(filename);
imports.push(
`import ${variable} from './${themeComponentsFolder}/${filename}';\n`
);
// Don't format as the generated file formatting will get wonky!
const componentString = `registerStyles(
'${tag}',
css\`
\${unsafeCSS(${variable}.toString())}
\`
);
`;
componentCssCode.push(componentString);
});

const themeIdentifier = '_vaadinds_' + themeName + '_';
const globalCssFlag = themeIdentifier + 'globalCss';
const componentCssFlag = themeIdentifier + 'componentCss';

themeFile += imports.join('');

// Don't format as the generated file formatting will get wonky!
// If targets check that we only register the style parts once, checks exist for global css and component css
const themeFileApply = `export const applyTheme = (target) => {
if (!target['${globalCssFlag}']) {
${globalCssCode.join('')}
target['${globalCssFlag}'] = true;
}
if (!document['${componentCssFlag}']) {
${componentCssCode.join('')}
document['${componentCssFlag}'] = true;
}
}
`;

Expand All @@ -94,7 +129,7 @@ function generateThemeFile(themeFolder, themeName) {
function camelCase(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '').replace(/\./g, '');
}).replace(/\s+/g, '').replace(/\.|\-/g, '');
}

module.exports = generateThemeFile;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[part='radio'] {
background-color: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[part="input-field"] {
background: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.uitest.ui.theme;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;

/**
* LIT version of vaadin radio button for testing component theming.
*/
@JsModule("@vaadin/vaadin-radio-button/vaadin-radio-button.js")
@Tag("vaadin-radio-button")
@NpmPackage(value = "@vaadin/vaadin-radio-button", version = "2.0.0-alpha1")
public class MyLitField extends Component {

/**
* Set the component id.
*
* @param id
* value to set
* @return this component
*/
public Component withId(String id) {
setId(id);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.uitest.ui.theme;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;

/**
* Polymer version of vaadin text field for testing component theming.
*/
@JsModule("@vaadin/vaadin-text-field/vaadin-text-field.js")
@Tag("vaadin-text-field")
@NpmPackage(value="@vaadin/vaadin-text-field", version = "2.7.1")
public class MyPolymerField extends Component {

/**
* Set the component id.
*
* @param id
* value to set
* @return this component
*/
public Component withId(String id) {
setId(id);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@

package com.vaadin.flow.uitest.ui.theme;

import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.Theme;

@Route("com.vaadin.flow.uitest.ui.theme.ThemeView")
@Theme(themeFolder = "app-theme")
@NpmPackage(value = "@vaadin/vaadin-themable-mixin", version = "1.6.1")
public class ThemeView extends Div {

public static final String MY_POLYMER_ID = "field";
public static final String MY_LIT_ID = "button";
public static final String TEST_TEXT_ID = "test-text";

public ThemeView() {
Expand All @@ -33,5 +37,9 @@ public ThemeView() {
Span subCss = new Span();
subCss.setId("sub-component");
add(textSpan, subCss);
add(new Div());
add(new MyPolymerField().withId(MY_POLYMER_ID));
add(new Div());
add(new MyLitField().withId(MY_LIT_ID));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

import com.vaadin.flow.component.html.testbench.SpanElement;
import com.vaadin.flow.testutil.ChromeBrowserTest;
import com.vaadin.testbench.TestBenchElement;

import static com.vaadin.flow.uitest.ui.theme.ThemeView.MY_LIT_ID;
import static com.vaadin.flow.uitest.ui.theme.ThemeView.MY_POLYMER_ID;

public class ThemeIT extends ChromeBrowserTest {

Expand Down Expand Up @@ -55,6 +59,26 @@ public void applicationTheme_GlobalCss_isUsed() {
driver.getPageSource().contains("Could not navigate"));
}

@Test
public void componentThemeIsApplied_forPolymerAndLit() {
open();
TestBenchElement myField = $(TestBenchElement.class).id(MY_POLYMER_ID);
TestBenchElement input = myField.$(TestBenchElement.class)
.id("vaadin-text-field-input-0");
Assert.assertEquals("Polymer text field should have red background",
"rgba(255, 0, 0, 1)", input.getCssValue("background-color"));

myField = $(TestBenchElement.class).id(MY_LIT_ID);
final SpanElement radio = myField.$(SpanElement.class).all().stream()
.filter(element -> "radio".equals(element.getAttribute("part")))
.findFirst().orElseGet(null);

Assert.assertNotNull("Element with part='radio' was not found", radio);

Assert.assertEquals("Lit radiobutton should have red background",
"rgba(255, 0, 0, 1)", radio.getCssValue("background-color"));
}

@Test
public void subCssWithRelativePath_urlPathIsNotRelative() {
open();
Expand Down

0 comments on commit b9079f4

Please sign in to comment.