18 changed files with 3259 additions and 10951 deletions
@ -1,238 +0,0 @@ |
|||||
#!/usr/bin/env node
|
|
||||
|
|
||||
import {Command} from 'commander'; |
|
||||
import path from 'path'; |
|
||||
import fetch from "node-fetch"; |
|
||||
import fs from "fs"; |
|
||||
import http from "http"; |
|
||||
import https from "https"; |
|
||||
import StreamZip from "node-stream-zip"; |
|
||||
import {fileURLToPath} from 'url'; |
|
||||
import memFs from 'mem-fs'; |
|
||||
import editor from 'mem-fs-editor'; |
|
||||
|
|
||||
export const defaultGitRepo = 'git+https://git.devdevdev.life/axe-web-public/block-dev-tool.git#master'; |
|
||||
|
|
||||
const __filename = fileURLToPath(import.meta.url); |
|
||||
const __dirname = path.dirname(__filename); |
|
||||
|
|
||||
const isDev = process.env.NODE_ENV === 'development'; // Check README file in case you get "missing files" error.
|
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life'; |
|
||||
const blocksDirectory = isDev ? 'blocks/' : ''; |
|
||||
|
|
||||
if (isDev) { |
|
||||
console.log(`Development Mode Active`); |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
const blockName = await init(); |
|
||||
console.log(`🎉 Done! \n\nCheck the "${blocksDirectory}${blockName}" directory. \n`); |
|
||||
} catch (e) { |
|
||||
console.log(`❌ Fail.`); |
|
||||
} |
|
||||
|
|
||||
async function init() { |
|
||||
const program = new Command(); |
|
||||
|
|
||||
program |
|
||||
.name('create-block') |
|
||||
.description('AXE-WEB Platform Blocks'); |
|
||||
|
|
||||
const promise = new Promise((resolve, reject) => { |
|
||||
program.command('pull') |
|
||||
.argument('<string>', 'Provide a full name of required block, for example: @axe-web/hero-block') |
|
||||
.action(async (blockName, options) => { |
|
||||
console.log('📦 Block to download:', blockName) |
|
||||
|
|
||||
const status = await getBlockSourceFiles(blockName); |
|
||||
if (status) { |
|
||||
resolve(blockName); |
|
||||
} else { |
|
||||
reject(); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
program.parse(); |
|
||||
return promise; |
|
||||
} |
|
||||
|
|
||||
async function getBlockSourceFiles(blockName) { |
|
||||
const queryString = new URLSearchParams(); |
|
||||
queryString.append('blockName', blockName); |
|
||||
queryString.append('includeDevConfig', 'true'); |
|
||||
|
|
||||
const response = await fetch(`${blocksRegistry}?${queryString.toString()}`); |
|
||||
const responseData = await response.json(); |
|
||||
if (!responseData || !responseData.name) { |
|
||||
console.log("⚠️ Block not found, please contact administrator."); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (responseData.status && responseData.status !== 200) { |
|
||||
console.log("⚠️ [ERROR]", responseData.message || "Server side error."); |
|
||||
throw new Error("⚠️ [ERROR]", responseData.message || "Server side error."); |
|
||||
} |
|
||||
|
|
||||
const data = { |
|
||||
title: responseData.title, |
|
||||
name: responseData.name, |
|
||||
preview_image: responseData.preview_image, |
|
||||
remToPx: responseData.config.remToPx ?? 16, |
|
||||
blockFilename: responseData.name, |
|
||||
blockClassName: responseData.name, |
|
||||
blockGroupName: responseData.project, |
|
||||
version: responseData.version, |
|
||||
devToolSource: defaultGitRepo, |
|
||||
}; |
|
||||
|
|
||||
if (responseData.downloadUrl) { |
|
||||
console.log(`⬇️ Downloading v${responseData.version}`); |
|
||||
const zipFile = await downloadFile(responseData.downloadUrl, responseData.name + '.zip'); |
|
||||
|
|
||||
// Download, Extract and Remove downloaded file.
|
|
||||
try { |
|
||||
const zip = new StreamZip.async({file: zipFile}); |
|
||||
await zip.extract(null, `${blocksDirectory}${responseData.name}/src`); |
|
||||
await zip.close(); |
|
||||
|
|
||||
await fs.promises.access(zipFile, fs.constants.W_OK); |
|
||||
await fs.promises.unlink(zipFile); |
|
||||
} catch (e) { |
|
||||
throw e; |
|
||||
} |
|
||||
} else { |
|
||||
await createSourceFiles(data, __dirname, `${blocksDirectory}${responseData.name}`); |
|
||||
} |
|
||||
|
|
||||
await createTechnicalFiles(data, __dirname, `${blocksDirectory}${responseData.name}`); |
|
||||
|
|
||||
if (responseData.config.design_files) { |
|
||||
await downloadDesignFiles(responseData.name, responseData.config.design_files); |
|
||||
} |
|
||||
|
|
||||
await createDataFiles(responseData.name, responseData.config.data_sources); |
|
||||
|
|
||||
await createConfigFile(responseData.name, responseData.config); |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
async function createConfigFile(blockName, config = {}) { |
|
||||
const obj = { |
|
||||
blockName, |
|
||||
baseView: config.baseView, |
|
||||
remToPx: config.remToPx, |
|
||||
}; |
|
||||
|
|
||||
const cssAssets = config.assets.filter(item => item.type === 'css').map(item => item.url); |
|
||||
if (cssAssets.length) { |
|
||||
obj.cssUrl = cssAssets; |
|
||||
} |
|
||||
|
|
||||
const jsAssets = config.assets.filter(item => item.type === 'js').map(item => item.url); |
|
||||
if (jsAssets.length) { |
|
||||
obj.jsUrl = jsAssets; |
|
||||
} |
|
||||
|
|
||||
await setupPath(`${blocksDirectory}${blockName}/config`); |
|
||||
await fs.promises.writeFile(`${blocksDirectory}${blockName}/config/default.json`, JSON.stringify(obj, null, 2)); |
|
||||
} |
|
||||
|
|
||||
async function downloadDesignFiles(blockName = '', files) { |
|
||||
for (let url of files) { |
|
||||
const fileName = url.split('/').pop(); |
|
||||
await setupPath(`${blocksDirectory}${blockName}/design`); |
|
||||
await downloadFile(url, `${blocksDirectory}${blockName}/design/${fileName}`) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async function createDataFiles(blockName, dataSources = []) { |
|
||||
for (let source of dataSources) { |
|
||||
await setupPath(`${blocksDirectory}${blockName}/data`); |
|
||||
|
|
||||
await fs.promises.writeFile(`${blocksDirectory}${blockName}/data/${source.name}.json`, JSON.stringify(source.data, null, 2)); |
|
||||
|
|
||||
if (typeof source.preview_images !== 'undefined' && source.preview_images.length) { |
|
||||
for (let preview_image of source.preview_images) { |
|
||||
await setupPath(`${blocksDirectory}${blockName}/design/preview`); |
|
||||
await downloadFile(preview_image.url, `${blocksDirectory}${blockName}/design/preview/${source.name}.${preview_image.width}.${preview_image.extension}`) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//
|
|
||||
// Helpers
|
|
||||
//
|
|
||||
|
|
||||
async function setupPath(path) { |
|
||||
if (!fs.existsSync(path)) { |
|
||||
await fs.promises.mkdir(path); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async function downloadFile(url, fileName) { |
|
||||
const file = fs.createWriteStream(fileName); |
|
||||
|
|
||||
return new Promise((resolve, reject) => { |
|
||||
const protocol = url.startsWith('https://') ? https : http; |
|
||||
protocol.get(url, function (response) { |
|
||||
response.pipe(file); |
|
||||
|
|
||||
// Loading Indicator.
|
|
||||
const loadingInterval = setInterval(() => console.log('🕐 Download in progress...'), 3000); |
|
||||
|
|
||||
// after download completed close filestream
|
|
||||
file.on("finish", () => { |
|
||||
clearInterval(loadingInterval); |
|
||||
|
|
||||
file.close(); |
|
||||
resolve(fileName); |
|
||||
}); |
|
||||
}); |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
export async function createSourceFiles(data, baseDir, distPath) { |
|
||||
const pathDist = distPath + '/src'; //path.join(baseDir, distPath);
|
|
||||
const generatorsPath = path.join(baseDir, 'generators/block/templates/src'); |
|
||||
|
|
||||
const store = memFs.create(); |
|
||||
const filesystem = editor.create(store); |
|
||||
|
|
||||
const files = [ |
|
||||
{from: 'template.template.hbs', to: `${data.name}.template.hbs`}, |
|
||||
{from: 'styles/template.scss', to: `styles/${data.name}.scss`}, |
|
||||
{from: 'scripts/template.js', to: `scripts/${data.name}.js`}, |
|
||||
'images/demo.jpeg', |
|
||||
]; |
|
||||
|
|
||||
for (let file of files) { |
|
||||
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`; |
|
||||
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`; |
|
||||
|
|
||||
await filesystem.copyTplAsync(from, to, data); |
|
||||
} |
|
||||
|
|
||||
return filesystem.commit(); // Promise
|
|
||||
} |
|
||||
|
|
||||
export async function createTechnicalFiles(data, baseDir, distPath) { |
|
||||
const pathDist = distPath; //path.join(baseDir, distPath);
|
|
||||
const generatorsPath = path.join(baseDir, 'generators/block/templates'); |
|
||||
|
|
||||
const store = memFs.create(); |
|
||||
const filesystem = editor.create(store); |
|
||||
|
|
||||
const files = ['package.json', 'README.md', '.editorconfig', {from: 'gitignore', to: '.gitignore'}, 'block.json']; |
|
||||
|
|
||||
for (let file of files) { |
|
||||
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`; |
|
||||
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`; |
|
||||
|
|
||||
await filesystem.copyTplAsync(from, to, data); |
|
||||
} |
|
||||
|
|
||||
return filesystem.commit(); // Promise
|
|
||||
} |
|
||||
@ -1,23 +0,0 @@ |
|||||
# This file is for unifying the coding style for different editors and IDEs |
|
||||
# editorconfig.org |
|
||||
|
|
||||
# WordPress Coding Standards |
|
||||
# https://make.wordpress.org/core/handbook/coding-standards/ |
|
||||
|
|
||||
root = true |
|
||||
|
|
||||
[*] |
|
||||
charset = utf-8 |
|
||||
end_of_line = lf |
|
||||
insert_final_newline = true |
|
||||
trim_trailing_whitespace = true |
|
||||
indent_style = space |
|
||||
indent_size = 2 |
|
||||
|
|
||||
[*.json] |
|
||||
indent_style = tab |
|
||||
indent_size = 4 |
|
||||
|
|
||||
[*.md] |
|
||||
trim_trailing_whitespace = false |
|
||||
|
|
||||
@ -1,7 +0,0 @@ |
|||||
# Basic |
|
||||
.idea |
|
||||
.DS_Store |
|
||||
node_modules |
|
||||
vendor |
|
||||
|
|
||||
# Custom |
|
||||
@ -1,167 +0,0 @@ |
|||||
# Development Setup |
|
||||
|
|
||||
Install required modules: |
|
||||
|
|
||||
```sh |
|
||||
npm install |
|
||||
``` |
|
||||
|
|
||||
Run development environment: |
|
||||
|
|
||||
```sh |
|
||||
npm start |
|
||||
``` |
|
||||
|
|
||||
## How it works ⚙️ |
|
||||
|
|
||||
The project runs `gulp` and `browsersync` beyond the scenes. |
|
||||
Each change in template, styling or script files will reload the page. The idea is to have a comfortable development |
|
||||
environment. |
|
||||
|
|
||||
# Block Structure |
|
||||
|
|
||||
You will find all the block files in `/src` folder. |
|
||||
|
|
||||
``` |
|
||||
/src/images/ |
|
||||
*.png, *jpg, *.svg |
|
||||
|
|
||||
/src/scritps/ |
|
||||
*.mjs, *js |
|
||||
|
|
||||
/src/styles/ |
|
||||
*.scss |
|
||||
|
|
||||
/src/*.template.hbs |
|
||||
``` |
|
||||
|
|
||||
# Block rules |
|
||||
|
|
||||
We have a “blocks system”, means you will have to follow strict rules and use listed technologies: |
|
||||
|
|
||||
## Template Layout & Data |
|
||||
|
|
||||
### Data |
|
||||
|
|
||||
You will find multiple `*.json` files in `/data` folder after the first run of `npm start`. |
|
||||
|
|
||||
These files are used as data sources in layout. By reviewing these files you can understand what parameters you have and |
|
||||
how the data is stored. |
|
||||
|
|
||||
There are multiple data sources since the block can be reused in different situations with different data. We have to |
|
||||
make sure that the block is always rendered properly. |
|
||||
|
|
||||
> Don't change or edit these files - use the data as it is! |
|
||||
|
|
||||
### Handlebars |
|
||||
|
|
||||
[Handlebars](https://handlebarsjs.com/guide/ "What is Handlebars") used as a template engine in the project. |
|
||||
|
|
||||
All the template layout must be in a single `*.hbs` file that located in `/src` folder. |
|
||||
|
|
||||
> Use the data that is available in `/data` folder. |
|
||||
> Avoid using any kind of "freestyle" input/content. Your template file must contain only HTML tags and Handlebars parameters. |
|
||||
|
|
||||
### Static Media Files 🖼️ |
|
||||
|
|
||||
Use the `/src/images` folder in case you need to upload images or video files to the template. |
|
||||
These files will be available by the next URL: http://localhost:3000/images/${filename} |
|
||||
|
|
||||
### Class Naming Convention |
|
||||
|
|
||||
Use BEM naming system for class names: http://getbem.com/naming/ |
|
||||
|
|
||||
## Styling rules 🎨 |
|
||||
|
|
||||
### Mobile First 📱 |
|
||||
|
|
||||
Use Mobile First approach. Start with mobile device styling and then focus on other screen sizes by using @media ( |
|
||||
min-width: ...px). |
|
||||
|
|
||||
### CSS Units |
|
||||
|
|
||||
Use `rem` units instead of pixels. |
|
||||
For example, `1rem = 16pixels` depends on project. |
|
||||
|
|
||||
No need to use rem in situations of `1px`, `2px` or `3px`. (usually used for borders width). |
|
||||
It's important to mention that the block is "elastic". By using `rem` units we are able to scale the layout and keep the |
|
||||
aspect ratio. |
|
||||
|
|
||||
### Global Styling |
|
||||
|
|
||||
The development environment includes CSS rules of the original project. This is including fonts, CSS variables, |
|
||||
container logic etc... |
|
||||
|
|
||||
The CSS file is already embedded in served HTML. |
|
||||
|
|
||||
# JavaScript |
|
||||
|
|
||||
## Sliders |
|
||||
|
|
||||
For any kind of slides animations - use [SwiperJS](https://swiperjs.com/get-started). |
|
||||
|
|
||||
The lib is included in the project and ready for use. Test with `window.Swiper` in browser console. |
|
||||
|
|
||||
# Summary |
|
||||
|
|
||||
## Short Video Review |
|
||||
Check the [Video Guide](https://www.loom.com/share/1c707a4ea14e48b7a35a49d7b0a6f1e0) |
|
||||
> PRO TIP |
|
||||
> Use x2 speed in the player 😜 |
|
||||
|
|
||||
|
|
||||
## Testing ✅ |
|
||||
|
|
||||
### Development Toolbar ✔️ |
|
||||
|
|
||||
Use development toolbar to switch between data sources. |
|
||||
|
|
||||
### Conditional Statements |
|
||||
Make sure the data is available in Handlebars template. |
|
||||
If there is a chance that some data/property will be missing - wrap the HTML layout with conditional verification: |
|
||||
|
|
||||
Example: |
|
||||
Render the link only if URL exists |
|
||||
``` |
|
||||
{{#if link_cta_url }} |
|
||||
<a href="{{link_cta_url}}">Click Here</a> |
|
||||
{{ endif }} |
|
||||
``` |
|
||||
|
|
||||
### Hover/Focus States ✔️ |
|
||||
|
|
||||
Make sure all "clickable" elements have `&:hover` & `&:focus` states in CSS rules. |
|
||||
This might be related to links, buttons or any other interactive elements. |
|
||||
|
|
||||
### Responsiveness ✔️ |
|
||||
|
|
||||
Make sure the layout is rendered correctly on all standard breakpoint size: |
|
||||
|
|
||||
1. 1920px in width... |
|
||||
2. 1680px |
|
||||
3. 1440px |
|
||||
4. 1360px |
|
||||
5. 1280px |
|
||||
6. 1024px |
|
||||
7. 980px |
|
||||
8. 768px |
|
||||
9. 600px |
|
||||
10. 414px |
|
||||
11. 375px |
|
||||
|
|
||||
If you follow the rules of REM unit - your layout must be "elastic" and most of the breakpoints should be rendered |
|
||||
properly. |
|
||||
|
|
||||
### Pixel Perfect Test ✔️ |
|
||||
|
|
||||
Compare the final result with provided screenshots. |
|
||||
|
|
||||
You can use |
|
||||
available [Browser Extensions](https://chrome.google.com/webstore/detail/perfectpixel-by-welldonec/dkaagdgjmgdmbnecmcefdhjekcoceebi?hl=en) |
|
||||
or any other tool/approach that you're familiar with. |
|
||||
The idea is to have "pretty close" result to requested design. |
|
||||
|
|
||||
## Delivery |
|
||||
Once you finished with the task, before letting me know - please review the next [Google Form link](https://forms.gle/w4sJjAyRDSSBRuGLA](https://forms.gle/w4sJjAyRDSSBRuGLA) |
|
||||
|
|
||||
If everything is OK - send a Pull Request to the git project or share a ZIP file with me directly. |
|
||||
@ -1,34 +0,0 @@ |
|||||
{ |
|
||||
"name": "<%= blockGroupName %>/<%= blockFilename %>", |
|
||||
"version": "<%= version %>", |
|
||||
"title": "<%= title %>", |
|
||||
"categories": [], |
|
||||
"icon": "shortcode", |
|
||||
"preview_image": "<%= preview_image %>", |
|
||||
"field_groups": [ |
|
||||
{ |
|
||||
"name": "content", |
|
||||
"label": "Content", |
|
||||
"fields": { |
|
||||
"title": { |
|
||||
"type": "text", |
|
||||
"label": "Title" |
|
||||
}, |
|
||||
"content": { |
|
||||
"type": "wysiwyg", |
|
||||
"label": "Content" |
|
||||
}, |
|
||||
"cta": { |
|
||||
"type": "link", |
|
||||
"label": "CTA" |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"name": "styling", |
|
||||
"label": "Styling", |
|
||||
"fields": { |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
module.exports = { |
|
||||
cssUrl: "https://", |
|
||||
jsUrl: "https://", |
|
||||
blockName: "<%= blockFilename %>", |
|
||||
baseView: "<%= baseView %>", |
|
||||
remToPx: <%= remToPx %>, |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
{ |
|
||||
"title": "Option Two", |
|
||||
"content": "<p><b>Delivering top results to industry leaders:</b></p>", |
|
||||
"image_url": "https://fakeimg.pl/400x400/", |
|
||||
"cta_text": "Our Success Stories", |
|
||||
"url": "https://google.com" |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Tips for good data.json example: |
|
||||
* - Provide Short and Long version of text (title/content). |
|
||||
* - Put <a> and <strong> elements in content. |
|
||||
* - Upload different sizes and ratios of images. |
|
||||
* - Provide options with and without CTA to test conditional logic. |
|
||||
*/ |
|
||||
@ -1,7 +0,0 @@ |
|||||
{ |
|
||||
"title": "As a Global Salesforce Partner,\n we deliver Service Cloud solutions\n and complex Field Service Management\n in diverse industries worldwide", |
|
||||
"content": "<p><b>Delivering top results to industry leaders:</b></p>", |
|
||||
"image_url": "https://fakeimg.pl/400x400/", |
|
||||
"cta_text": "Our Success Stories", |
|
||||
"url": "https://google.com" |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
# Basic |
|
||||
.idea |
|
||||
.DS_Store |
|
||||
node_modules |
|
||||
vendor |
|
||||
|
|
||||
# Custom |
|
||||
@ -1,11 +0,0 @@ |
|||||
{ |
|
||||
"name": "<%= blockFilename %>", |
|
||||
"version": "<%= version %>", |
|
||||
"scripts": { |
|
||||
"start": "component-dev", |
|
||||
"build": "component-build" |
|
||||
}, |
|
||||
"devDependencies": { |
|
||||
"create-block-dev-tool": "<%= devToolSource %>" |
|
||||
} |
|
||||
} |
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@ -1,7 +0,0 @@ |
|||||
(function ($) { |
|
||||
|
|
||||
console.log('Ready!'); |
|
||||
console.log('jQuery =', $); |
|
||||
console.log('Swiper =', window.Swiper); |
|
||||
|
|
||||
})(window.jQuery); |
|
||||
@ -1,40 +0,0 @@ |
|||||
/** |
|
||||
* Use "rem" instead of pixels. 1rem = <%= remToPx %>pixels |
|
||||
* No need to use rem in situations of "1px". (usually used for borders width). |
|
||||
* |
|
||||
* Use BEM naming system for class names: http://getbem.com/naming/ |
|
||||
* |
|
||||
* Use Mobile First approach. |
|
||||
* Start with mobile device styling and then focus on other screen sizes by using @media (min-width: ...px). |
|
||||
* |
|
||||
*/ |
|
||||
|
|
||||
.<%= blockClassName %> { |
|
||||
|
|
||||
&__header { |
|
||||
// Header Mobile. |
|
||||
|
|
||||
@media (min-width: 1024px) { |
|
||||
// Header Desktop. |
|
||||
} |
|
||||
|
|
||||
&-heading { |
|
||||
// Child Element. |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&__visual { |
|
||||
&-image { |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&__content { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
&__footer { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,68 +0,0 @@ |
|||||
<section class="<%= blockClassName %> <%= blockClassName %>--{{ config.activeDataFile }}"> |
|
||||
|
|
||||
{{! |
|
||||
Remove Everything Below: |
|
||||
}} |
|
||||
|
|
||||
<style> |
|
||||
code { |
|
||||
background-color: #eee; |
|
||||
color: #333; |
|
||||
padding: 1rem 1.5rem; |
|
||||
border: 2px solid #d8d8d8; |
|
||||
display: inline-block; |
|
||||
border-radius: 2px; |
|
||||
font-size: 14px; |
|
||||
white-space: pre-line; |
|
||||
} |
|
||||
</style> |
|
||||
|
|
||||
<header class="<%= blockClassName %>__header"> |
|
||||
<h1 class="<%= blockClassName %>__header-heading">Ready!</h1> |
|
||||
</header> |
|
||||
|
|
||||
<div class="<%= blockClassName %>__content"> |
|
||||
<p> |
|
||||
Review the `/data` folder with JSON data files.<br> |
|
||||
Don't change data JSON files - use the data as it is. |
|
||||
</p> |
|
||||
<p>Build the layout based on provided data structure.</p> |
|
||||
|
|
||||
<br><br> |
|
||||
<hr> |
|
||||
|
|
||||
<h2>Image Example</h2> |
|
||||
<div> |
|
||||
<img src="/images/demo.jpeg" alt=""> |
|
||||
<br> |
|
||||
<code><img src="/images/demo.jpeg" alt=""> |
|
||||
</code> |
|
||||
</div> |
|
||||
|
|
||||
<br><br> |
|
||||
<hr> |
|
||||
|
|
||||
<h2>Available Buttons Styling</h2> |
|
||||
|
|
||||
<div> |
|
||||
<a href="#" class="btn btn--primary">Primary</a> |
|
||||
<br> |
|
||||
<code><a href="#" class="btn btn--primary">Primary</a> |
|
||||
</code> |
|
||||
</div> |
|
||||
|
|
||||
<br> |
|
||||
|
|
||||
<div> |
|
||||
<a href="#" class="btn btn--secondary">Secondary</a> |
|
||||
<br> |
|
||||
<code><a href="#" class="btn btn--secondary">Secondary</a> |
|
||||
</code> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{{! |
|
||||
Remove END |
|
||||
}} |
|
||||
|
|
||||
</section> |
|
||||
@ -1,14 +0,0 @@ |
|||||
export function capitalize(str) { |
|
||||
if (typeof str !== 'string') { |
|
||||
return ''; |
|
||||
} |
|
||||
|
|
||||
return str |
|
||||
.toLowerCase() |
|
||||
.split(/[ -_]/g) |
|
||||
.filter((word) => !!word) |
|
||||
.map((word) => { |
|
||||
return word.charAt(0).toUpperCase() + word.slice(1); |
|
||||
}) |
|
||||
.join(' '); |
|
||||
} |
|
||||
File diff suppressed because it is too large
Loading…
Reference in new issue