├── .angular-cli.json ├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── .yo-rc.json ├── LICENSE ├── README.MD ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── gulpfile.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app.component.ts │ ├── app.module.ts │ ├── doc │ │ ├── components │ │ │ ├── badge.component.html │ │ │ ├── badge.component.ts │ │ │ ├── banner.component.html │ │ │ ├── banner.component.ts │ │ │ ├── breadcrumbs.component.html │ │ │ ├── breadcrumbs.component.ts │ │ │ ├── checkbox.component.html │ │ │ ├── checkbox.component.ts │ │ │ ├── choice.component.html │ │ │ ├── choice.component.ts │ │ │ ├── choice.list.component.html │ │ │ ├── choice.list.component.ts │ │ │ ├── index.ts │ │ │ ├── radio.button.component.html │ │ │ └── radio.button.component.ts │ │ ├── doc.component.html │ │ ├── doc.component.ts │ │ ├── doc.data.ts │ │ ├── doc.module.ts │ │ ├── doc.service.ts │ │ └── utilities │ │ │ ├── code.card.component.ts │ │ │ ├── component.component.ts │ │ │ ├── component.doc.wrapper.component.html │ │ │ ├── component.doc.wrapper.component.ts │ │ │ ├── list.component.html │ │ │ ├── list.component.ts │ │ │ └── meta.component.ts │ ├── home.component.html │ ├── home.component.ts │ ├── library │ │ ├── AngularPolarisModule.ts │ │ ├── badge │ │ │ ├── badge.component.html │ │ │ └── badge.component.ts │ │ ├── banner │ │ │ ├── banner.component.html │ │ │ ├── banner.component.ts │ │ │ └── index.ts │ │ ├── breadcrumbs │ │ │ ├── breadcrumbs.component.html │ │ │ └── breadcrumbs.component.ts │ │ ├── button │ │ │ ├── button.component.html │ │ │ ├── button.component.ts │ │ │ ├── button.group.component.html │ │ │ └── button.group.component.ts │ │ ├── card │ │ │ ├── card.component.html │ │ │ ├── card.component.ts │ │ │ ├── card.section.component.ts │ │ │ ├── header.component.html │ │ │ └── header.component.ts │ │ ├── checkbox │ │ │ ├── checkbox.component.html │ │ │ ├── checkbox.component.ts │ │ │ └── index.ts │ │ ├── choice.list │ │ │ ├── choice.list.component.html │ │ │ ├── choice.list.component.ts │ │ │ └── index.ts │ │ ├── choice │ │ │ ├── choice.component.html │ │ │ ├── choice.component.ts │ │ │ └── index.ts │ │ ├── form │ │ │ ├── animations.ts │ │ │ ├── element.base.ts │ │ │ ├── validate.ts │ │ │ └── value.accessor.ts │ │ ├── icon │ │ │ ├── icon.component.html │ │ │ └── icon.component.ts │ │ ├── index.ts │ │ ├── label │ │ │ ├── label.component.html │ │ │ └── label.component.ts │ │ ├── labelled │ │ │ ├── labelled.component.html │ │ │ └── labelled.component.ts │ │ ├── layout │ │ │ ├── layout.annotated.section.component.html │ │ │ ├── layout.annotated.section.component.ts │ │ │ ├── layout.component.ts │ │ │ └── section.component.ts │ │ ├── page │ │ │ ├── page.component.html │ │ │ ├── page.component.ts │ │ │ ├── page.header.component.html │ │ │ └── page.header.component.ts │ │ ├── pagination │ │ │ ├── index.ts │ │ │ ├── pagination.component.html │ │ │ ├── pagination.component.ts │ │ │ └── pagination.descriptor.ts │ │ ├── radio.button │ │ │ ├── index.ts │ │ │ ├── radio.button.component.html │ │ │ └── radio.button.component.ts │ │ ├── resource.list │ │ │ ├── resource.list.component.html │ │ │ ├── resource.list.component.ts │ │ │ ├── resource.list.item.component.html │ │ │ └── resource.list.item.component.ts │ │ ├── select │ │ │ ├── select.component.html │ │ │ └── select.component.ts │ │ ├── stack │ │ │ ├── stack.component.ts │ │ │ └── stack.item.component.ts │ │ ├── text.field │ │ │ ├── resizer.component.html │ │ │ ├── resizer.component.ts │ │ │ ├── spinner.component.html │ │ │ ├── spinner.component.ts │ │ │ ├── text.field.component.html │ │ │ └── text.field.component.ts │ │ ├── thumbnail │ │ │ ├── thumbnail.component.html │ │ │ └── thumbnail.component.ts │ │ ├── types.ts │ │ ├── unstyled.link │ │ │ ├── unstyled.link.component.html │ │ │ └── unstyled.link.component.ts │ │ ├── utilities │ │ │ └── template.or.string.component.ts │ │ ├── validators │ │ │ └── hexadecimal-validator.ts │ │ ├── visually.hidden │ │ │ └── visually.hidden.component.ts │ │ └── wysiwyg │ │ │ ├── wysiwyg.component.css │ │ │ ├── wysiwyg.component.html │ │ │ └── wysiwyg.component.ts │ ├── not.found.component.html │ └── not.found.component.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── manifest.json ├── polyfills.ts ├── prism-coy.css ├── styles.css ├── test.ts ├── tsconfig.app.json ├── tsconfig.es5.json ├── tsconfig.spec.json └── typings.d.ts ├── tools └── gulp │ └── inline-resources.js ├── tsconfig.json └── tslint.json /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "angular-polaris" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "plrs", 21 | "styles": [ 22 | "styles.css", 23 | "prism-coy.css" 24 | ], 25 | "scripts": [ 26 | ], 27 | "environmentSource": "environments/environment.ts", 28 | "environments": { 29 | "dev": "environments/environment.ts", 30 | "prod": "environments/environment.prod.ts" 31 | } 32 | } 33 | ], 34 | "e2e": { 35 | "protractor": { 36 | "config": "./protractor.conf.js" 37 | } 38 | }, 39 | "lint": [ 40 | { 41 | "project": "src/tsconfig.app.json" 42 | }, 43 | { 44 | "project": "src/tsconfig.spec.json" 45 | }, 46 | { 47 | "project": "e2e/tsconfig.e2e.json" 48 | } 49 | ], 50 | "test": { 51 | "karma": { 52 | "config": "./karma.conf.js" 53 | } 54 | }, 55 | "defaults": { 56 | "styleExt": "css", 57 | "component": {} 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules/* 3 | npm-debug.log 4 | docs/* 5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM 6 | # TypeScript 7 | # *.js 8 | # *.map 9 | # *.d.ts 10 | 11 | # JetBrains 12 | .idea 13 | .project 14 | .settings 15 | .idea/* 16 | *.iml 17 | 18 | # VS Code 19 | .vscode/* 20 | 21 | # Windows 22 | Thumbs.db 23 | Desktop.ini 24 | 25 | # Mac 26 | .DS_Store 27 | **/.DS_Store 28 | 29 | # Ngc generated files 30 | **/*.ngfactory.ts 31 | 32 | # Library files 33 | src/* 34 | build/* 35 | 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '4.2.1' 5 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular-cli-library": { 3 | "promptValues": { 4 | "authorName": "Maxime Rainville", 5 | "authorEmail": "maxime@rainville.me", 6 | "prefix": "plrs", 7 | "gitRepositoryUrl": "https://github.com/syrp-nz/angular-polaris" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Syrp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # angular-polaris 2 | 3 | This package aimed to port functionality from the Shopify Polaris design system from React to Angular. It tries to follow the same convention as the original Shopify Polaris package as much as possible. 4 | 5 | ```html 6 | 7 | 8 | 9 |

Manage configuration.

10 |
11 |
12 |
13 | ``` 14 | 15 | [Review the Angular Poalris Demo Project](https://syrp-nz.github.io/angular-polaris/home) to see the library in action and see code examples. 16 | 17 | ## Under Development - Use at your own risk 18 | 19 | The package is under active development, so beware. Polaris components are converted as needed. 20 | 21 | | Component | Implementation state | Extra details | 22 | |--------------- |---------------------- |--------------- | 23 | | Badge | Beta | | 24 | | Banner | Beta | | 25 | | Breadcrumbs | Dev | | 26 | | Button | Beta | | 27 | | Card | Beta | | 28 | | Icon | Dev | | 29 | | Label | Dev | | 30 | | Labelled | Dev | | 31 | | Layout | Beta | | 32 | | Page | Dev | | 33 | | Pagination | Dev | | 34 | | Resource List | Dev | | 35 | | Select | Dev | | 36 | | Stack | Beta | | 37 | | Textfield | Dev | | 38 | | Thumbnail | Dev | | 39 | | UnstyledLink | Dev | | 40 | | Visually Hidden | Beta | | 41 | 42 | NB: The Angular Polaris component are prefix with `plrs`. e.g. 43 | 44 | ## Installation 45 | 46 | To install this library, run: 47 | 48 | ```bash 49 | $ npm install angular-polaris --save 50 | ``` 51 | 52 | ## Consuming the library 53 | To use AngularPolaris in your Angular project, import `AngularPolarisModule` in your `AppModule`: 54 | 55 | ```typescript 56 | import { BrowserModule } from '@angular/platform-browser'; 57 | import { NgModule } from '@angular/core'; 58 | 59 | import { AppComponent } from './app.component'; 60 | 61 | // Import your library 62 | import { AngularPolarisModule } from 'angular-polaris'; 63 | 64 | @NgModule({ 65 | declarations: [ 66 | AppComponent 67 | ], 68 | imports: [ 69 | BrowserModule, 70 | AngularPolarisModule 71 | ], 72 | providers: [], 73 | bootstrap: [AppComponent] 74 | }) 75 | export class AppModule { } 76 | ``` 77 | 78 | The package doesn't ship with the Polaris css, so you need to manually include it in your project from the Shopify CDN. e.g.: 79 | ```html 80 | 81 | ``` 82 | 83 | ## Development 84 | 85 | To generate all `*.js`, `*.d.ts` and `*.metadata.json` files: 86 | 87 | ```bash 88 | $ npm run publish 89 | ``` 90 | 91 | ## Wysiwyg Component 92 | 93 | The library comes with a `plrsWysiwyg` component that is not natively included in the Shopify Polaris React library. This component is built on top 94 | of (QuillJs)[https://quilljs.com]. If you want to use the WYSIWYG component, you need to add a reference to `"../node_modules/quill/dist/quill.js"` 95 | under `scripts` in your `.angular-cli.json` file and to load the Quill Snow Theme css in your `index.html` file with: 96 | ```html 97 | 98 | ``` 99 | 100 | The Angular implementation was done by adapting code from (NGX-Quill)[https://github.com/KillerCodeMonkey/ngx-quill]. 101 | 102 | ## License 103 | 104 | MIT © [Maxime Rainville](mailto:maxime@syrp.co.nz) 105 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { MyPage } from './app.po'; 2 | 3 | describe('my App', () => { 4 | let page: MyPage; 5 | 6 | beforeEach(() => { 7 | page = new MyPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class MyPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('plrs-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var gulp = require('gulp'), 3 | path = require('path'), 4 | ngc = require('@angular/compiler-cli/src/main').main, 5 | rollup = require('gulp-rollup'), 6 | rename = require('gulp-rename'), 7 | del = require('del'), 8 | runSequence = require('run-sequence'), 9 | inlineResources = require('./tools/gulp/inline-resources'), 10 | typedoc = require("gulp-typedoc"), 11 | replace = require('gulp-replace'); 12 | 13 | const rootFolder = path.join(__dirname); 14 | const srcFolder = path.join(rootFolder, 'src'); 15 | const tmpFolder = path.join(rootFolder, '.tmp'); 16 | const buildFolder = path.join(rootFolder, 'build'); 17 | const distFolder = path.join(rootFolder, 'dist'); 18 | const sources = [`${srcFolder}/app/library/**/*`, `!${srcFolder}/app/library/**/*.spec.ts`]; 19 | /** 20 | * library external dependencies 21 | */ 22 | const externalDependencies = { 23 | typescript: 'ts', 24 | '@angular/core': '@angular/core', 25 | '@angular/common': '@angular/common', 26 | 'rxjs/Rx': 'rxjs/Rx' 27 | }; 28 | 29 | function resolveDependencies(id) { 30 | return externalDependencies[id]; 31 | } 32 | 33 | /** 34 | * 1. Delete /dist folder 35 | */ 36 | gulp.task('clean:dist', function () { 37 | return deleteFolders([distFolder]); 38 | }); 39 | 40 | /** 41 | * 2. Clone the /src/app/library folder into /.tmp/app/library excluding test files 42 | */ 43 | gulp.task('copy:source', function () { 44 | return gulp.src(sources, {base: `${srcFolder}/app/library`}) 45 | .pipe(gulp.dest(tmpFolder)); 46 | }); 47 | 48 | /** 49 | * 2.1. Clone the /src/index.ts and /src/tsconfig.es5.json folder into /.tmp 50 | */ 51 | gulp.task('copy:builDefinition', function () { 52 | return gulp.src([`${srcFolder}/tsconfig.es5.json`]) 53 | .pipe(gulp.dest(tmpFolder)); 54 | }); 55 | 56 | /** 57 | * 3. Inline template (.html) and style (.css) files into the the component .ts files. 58 | * We do this on the /.tmp folder to avoid editing the original /src files 59 | */ 60 | gulp.task('inline-resources', function () { 61 | return Promise.resolve() 62 | .then(() => inlineResources(tmpFolder)); 63 | }); 64 | 65 | 66 | /** 67 | * 4. Run the Angular compiler, ngc, on the /.tmp folder. This will output all 68 | * compiled modules to the /build folder. 69 | */ 70 | gulp.task('ngc', function () { 71 | return ngc({ 72 | project: `${tmpFolder}/tsconfig.es5.json` 73 | }) 74 | .then((exitCode) => { 75 | if (exitCode === 1) { 76 | // This error is caught in the 'compile' task by the runSequence method callback 77 | // so that when ngc fails to compile, the whole compile process stops running 78 | throw new Error('ngc compilation failed'); 79 | } 80 | }); 81 | }); 82 | 83 | /** 84 | * 5. Run rollup inside the /build folder to generate our Flat ES module and place the 85 | * generated file into the /dist folder 86 | */ 87 | gulp.task('rollup:fesm', function () { 88 | return gulp.src(`${buildFolder}/**/*.js`) 89 | // transform the files here. 90 | .pipe(rollup({ 91 | // Bundle's entry point 92 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry 93 | entry: `${buildFolder}/index.js`, 94 | 95 | // A list of IDs of modules that should remain external to the bundle 96 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external 97 | external: resolveDependencies, 98 | 99 | // Format of generated bundle 100 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format 101 | format: 'es' 102 | })) 103 | .pipe(gulp.dest(distFolder)); 104 | }); 105 | 106 | /** 107 | * 6. Run rollup inside the /build folder to generate our UMD module and place the 108 | * generated file into the /dist folder 109 | */ 110 | gulp.task('rollup:umd', function () { 111 | return gulp.src(`${buildFolder}/**/*.js`) 112 | // transform the files here. 113 | .pipe(rollup({ 114 | 115 | // Bundle's entry point 116 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry 117 | entry: `${buildFolder}/index.js`, 118 | 119 | // A list of IDs of modules that should remain external to the bundle 120 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external 121 | external: resolveDependencies, 122 | 123 | // Format of generated bundle 124 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format 125 | format: 'umd', 126 | 127 | // Export mode to use 128 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#exports 129 | exports: 'named', 130 | 131 | // The name to use for the module for UMD/IIFE bundles 132 | // (required for bundles with exports) 133 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#modulename 134 | moduleName: 'angular-polaris', 135 | 136 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals 137 | globals: externalDependencies 138 | 139 | })) 140 | .pipe(rename('angular-polaris.umd.js')) 141 | .pipe(gulp.dest(distFolder)); 142 | }); 143 | 144 | /** 145 | * 7. Copy all the files from /build to /dist, except .js files. We ignore all .js from /build 146 | * because with don't need individual modules anymore, just the Flat ES module generated 147 | * on step 5. 148 | */ 149 | gulp.task('copy:build', function () { 150 | return gulp.src([`${buildFolder}/**/*`, `!${buildFolder}/**/*.js`]) 151 | .pipe(gulp.dest(distFolder)); 152 | }); 153 | 154 | /** 155 | * 8. Copy manifest.json from /src to /dist/package.json 156 | */ 157 | gulp.task('copy:manifest', function () { 158 | return gulp.src([`${srcFolder}/manifest.json`]) 159 | .pipe(rename('package.json')) 160 | .pipe(gulp.dest(distFolder)); 161 | }); 162 | 163 | /** 164 | * 9. Delete /.tmp folder 165 | */ 166 | gulp.task('clean:tmp', function () { 167 | return deleteFolders([tmpFolder]); 168 | }); 169 | 170 | /** 171 | * 10. Delete /build folder 172 | */ 173 | gulp.task('clean:build', function () { 174 | return deleteFolders([buildFolder]); 175 | }); 176 | 177 | gulp.task('compile', function () { 178 | runSequence( 179 | 'clean:dist', 180 | 'copy:source', 181 | 'copy:builDefinition', 182 | 'inline-resources', 183 | 'ngc', 184 | 'rollup:fesm', 185 | 'rollup:umd', 186 | 'copy:build', 187 | 'copy:manifest', 188 | 'clean:build', 189 | 'clean:tmp', 190 | function (err) { 191 | if (err) { 192 | console.log('ERROR:', err.message); 193 | deleteFolders([distFolder, tmpFolder, buildFolder]); 194 | } else { 195 | console.log('Compilation finished succesfully'); 196 | } 197 | }); 198 | }); 199 | 200 | gulp.task("typedoc", function() { 201 | return gulp 202 | .src(sources) 203 | .pipe(typedoc({ 204 | module: "amd", 205 | out: "docs/", 206 | readme: "README.md", 207 | name: "angular-polaris", 208 | ignoreCompilerErrors: true 209 | })); 210 | }); 211 | 212 | gulp.task("format:doc", function () { 213 | return gulp 214 | .src("docs/**/*.html") 215 | .pipe(replace("_@_", "@")) 216 | .pipe(gulp.dest("docs/")); 217 | }); 218 | 219 | /** 220 | * Watch for any change in the /src folder and compile files 221 | */ 222 | gulp.task('watch', function () { 223 | gulp.watch(`${srcFolder}/**/*`, ['compile']); 224 | }); 225 | 226 | gulp.task('clean', ['clean:dist', 'clean:tmp', 'clean:build']); 227 | 228 | gulp.task('build', ['clean', 'compile']); 229 | gulp.task('build:watch', ['build', 'watch']); 230 | gulp.task('docs', function() { runSequence('typedoc', 'format:doc')}); 231 | gulp.task('docs:watch', ['docs', 'watch']); 232 | gulp.task('default', ['build:watch']); 233 | 234 | /** 235 | * Deletes the specified folder 236 | */ 237 | function deleteFolders(folders) { 238 | return del(folders); 239 | } 240 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | files: [ 19 | { pattern: './src/test.ts', watched: false } 20 | ], 21 | preprocessors: { 22 | './src/test.ts': ['@angular/cli'] 23 | }, 24 | mime: { 25 | 'text/x-typescript': ['ts','tsx'] 26 | }, 27 | coverageIstanbulReporter: { 28 | reports: [ 'html', 'lcovonly' ], 29 | fixWebpackSourcePaths: true 30 | }, 31 | angularCli: { 32 | environment: 'dev' 33 | }, 34 | reporters: config.angularCli && config.angularCli.codeCoverage 35 | ? ['progress', 'coverage-istanbul'] 36 | : ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome'], 42 | singleRun: false 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-polaris", 3 | "version": "0.0.5", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "predeploy:demo": "ng build --prod --base-href \"https://syrp-nz.github.io/angular-polaris/\"", 9 | "deploy:demo": "ngh", 10 | "test": "ng test", 11 | "coverage": "ng test --sr -cc", 12 | "lint": "ng lint", 13 | "e2e": "ng e2e", 14 | "publish": "gulp build", 15 | "docs": "gulp docs" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git@github.com:syrp-nz/angular-polaris.git" 20 | }, 21 | "author": { 22 | "name": "Maxime Rainville", 23 | "email": "maxime@syrp.co.nz" 24 | }, 25 | "keywords": [ 26 | "angular", 27 | "shopify", 28 | "polaris" 29 | ], 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/syrp-nz/angular-polaris/issues" 33 | }, 34 | "dependencies": { 35 | "@angular/common": "^4.0.0", 36 | "@angular/core": "^4.0.0", 37 | "@angular/forms": "^4.0.0", 38 | "@angular/platform-browser": "^4.0.0", 39 | "@angular/platform-browser-dynamic": "^4.0.0", 40 | "core-js": "^2.4.1", 41 | "quill": "^1.2.0", 42 | "rxjs": "^5.1.0", 43 | "zone.js": "^0.8.4" 44 | }, 45 | "devDependencies": { 46 | "@angular/cli": "^1.2.0", 47 | "@angular/compiler": "^4.2.6", 48 | "@angular/compiler-cli": "^4.2.4", 49 | "@angular/http": "^4.2.4", 50 | "@angular/router": "^4.2.4", 51 | "@shopify/images": "^1.1.3", 52 | "@shopify/javascript-utilities": "^1.1.6", 53 | "@types/jasmine": "2.5.38", 54 | "@types/node": "~6.0.60", 55 | "@types/prismjs": "^1.6.2", 56 | "@types/quill": "0.0.30", 57 | "codelyzer": "~2.0.0", 58 | "del": "^2.2.2", 59 | "gulp": "^3.9.1", 60 | "gulp-rename": "^1.2.2", 61 | "gulp-replace": "^0.5.4", 62 | "gulp-rollup": "^2.11.0", 63 | "gulp-typedoc": "^2.0.2", 64 | "jasmine-core": "~2.5.2", 65 | "jasmine-spec-reporter": "~3.2.0", 66 | "karma": "~1.4.1", 67 | "karma-chrome-launcher": "~2.1.1", 68 | "karma-cli": "~1.0.1", 69 | "karma-coverage-istanbul-reporter": "^0.2.0", 70 | "karma-jasmine": "~1.1.0", 71 | "karma-jasmine-html-reporter": "^0.2.2", 72 | "prismjs": "^1.6.0", 73 | "protractor": "~5.1.0", 74 | "rollup": "^0.41.6", 75 | "run-sequence": "^1.2.2", 76 | "ts-node": "~2.0.0", 77 | "tslint": "~4.5.0", 78 | "typedoc": "^0.7.1", 79 | "typescript": "~2.2.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | beforeLaunch: function() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | }, 27 | onPrepare() { 28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'plrs-root', 5 | template: ``, 6 | }) 7 | export class AppComponent {} 8 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | import { RouterModule, Routes } from '@angular/router'; 6 | import { AngularPolarisModule } from './library' 7 | import { DocModule } from './doc/doc.module' 8 | 9 | import { AppComponent } from './app.component'; 10 | import { HomeComponent } from './home.component'; 11 | import { NotFoundComponent } from './not.found.component'; 12 | 13 | const appRoutes: Routes = [ 14 | { path: 'home', component: HomeComponent }, 15 | { path: '', redirectTo: '/home', pathMatch: 'full' }, 16 | { path: '**', component: NotFoundComponent }, 17 | ]; 18 | 19 | @NgModule({ 20 | declarations: [ 21 | AppComponent, 22 | HomeComponent, 23 | NotFoundComponent 24 | ], 25 | imports: [ 26 | BrowserModule, 27 | FormsModule, 28 | HttpModule, 29 | AngularPolarisModule, 30 | DocModule, 31 | RouterModule.forRoot(appRoutes), 32 | ], 33 | providers: [], 34 | bootstrap: [AppComponent] 35 | }) 36 | export class AppModule { } 37 | -------------------------------------------------------------------------------- /src/app/doc/components/badge.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ content }} 3 |
4 | 5 | 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /src/app/doc/components/badge.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | 6 | @Component({ 7 | templateUrl: 'badge.component.html' 8 | }) 9 | export class BadgeComponent extends ComponentComponent { 10 | 11 | protected componentPath: string = 'badge/badge.component'; 12 | 13 | @Input() public content:string = 'Hello world'; 14 | @Input() public status:string|false = ''; 15 | @Input() public progress:string|false = ''; 16 | 17 | statusOptions = ['', 'default', 'success', 'info', 'warning', 'attention']; 18 | 19 | progressOptions = ['', 'incomplete', 'partiallyComplete', 'complete']; 20 | 21 | constructor(protected service: DocService) { 22 | super(service); 23 | } 24 | 25 | public get code(): string { 26 | const status = this.nullableAttr('status'); 27 | const progress = this.nullableAttr('progress'); 28 | 29 | return ` 30 | ${this.content} 31 | `; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/app/doc/components/banner.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ content }} 3 | 4 |
5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/doc/components/banner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewChild} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | import * as Polaris from '../../library'; 6 | import { Subscriber } from 'rxjs'; 7 | 8 | @Component({ 9 | templateUrl: 'banner.component.html' 10 | }) 11 | export class BannerComponent extends ComponentComponent { 12 | 13 | protected componentPath: string = 'banner/banner.component'; 14 | 15 | @ViewChild('banner') private banner: Polaris.Banner.BannerComponent; 16 | 17 | private dismissSubscriber: Subscriber; 18 | 19 | 20 | content:string = 'Hello world'; 21 | title:string = ''; 22 | icon:string = ''; 23 | status:string|false = ''; 24 | 25 | logDismiss: boolean = false; 26 | logPrimaryAction: boolean = false; 27 | logSecondaryAction: boolean = false; 28 | 29 | primaryAction: Polaris.Types.AngularComplexAction; 30 | secondaryAction: Polaris.Types.AngularComplexAction; 31 | 32 | statusOptions = ['', 'default', 'success', 'info', 'warning', 'critical']; 33 | 34 | constructor(protected service: DocService) { 35 | super(service); 36 | this.primaryAction = { 37 | content: 'Primary action', 38 | onAction: () => {this.eventLog(undefined, 'action')} 39 | } 40 | this.secondaryAction = { 41 | content: 'Secondary action', 42 | onAction: () => {this.eventLog(undefined, 'secondaryAction')} 43 | } 44 | } 45 | 46 | dismissUpdate() { 47 | if (this.logDismiss) { 48 | this.dismissSubscriber = this.banner.dismiss.subscribe((event) => {this.eventLog(event, 'dismiss')}); 49 | } else { 50 | this.dismissSubscriber.unsubscribe(); 51 | } 52 | } 53 | 54 | primaryActionUpdate() { 55 | if (this.logPrimaryAction) { 56 | this.banner.action = this.primaryAction; 57 | } else { 58 | this.banner.action = undefined; 59 | } 60 | } 61 | 62 | secondaryActionUpdate() { 63 | if (this.logSecondaryAction) { 64 | this.banner.secondaryAction = this.secondaryAction; 65 | } else { 66 | this.banner.secondaryAction = undefined; 67 | } 68 | } 69 | 70 | 71 | 72 | public get code(): string { 73 | const title = this.nullableAttr('title'); 74 | const status = this.nullableAttr('status'); 75 | const icon = this.nullableAttr('icon'); 76 | const dismiss = this.eventAttr('dismiss'); 77 | const action = this.logPrimaryAction ? this.indent(`[action]="{content: 'Primary action', onAction: eventLog}"`) : ''; 78 | const secondaryAction = this.logSecondaryAction ? this.indent(`[secondaryAction]="{content: 'Secondary action', onAction: eventLog}"`) : ''; 79 | 80 | return ` 81 | ${this.content} 82 | `; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/app/doc/components/breadcrumbs.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | Pop crumb 6 | Push router link crumb 7 | Push normal link crumb 8 | Push log action crumb 9 |
10 | 11 |

Breadcrumbs is a system component used by the page component. It's not meant to be used directly.

12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/doc/components/breadcrumbs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | 6 | import { Types } from '../../library'; 7 | 8 | @Component({ 9 | templateUrl: './breadcrumbs.component.html' 10 | }) 11 | export class BreadcrumbsComponent extends ComponentComponent { 12 | 13 | protected componentPath: string = 'breadcrumbs/breadcrumbs.component'; 14 | 15 | label:string = 'Hello world'; 16 | id:string = 'SampleID123'; 17 | helpText:string = ''; 18 | error:any = ''; 19 | 20 | breadcrumbs: Types.AngularComplexAction[] = [{content: "Root Page", url: "#"}]; 21 | 22 | constructor(protected service: DocService) { 23 | super(service); 24 | } 25 | 26 | public popAction() { 27 | if (this.breadcrumbs.length > 1) { 28 | this.breadcrumbs.pop(); 29 | } 30 | } 31 | 32 | public pushLogAction() { 33 | this.breadcrumbs.push({ 34 | content: "Log to console " + (this.breadcrumbs.length + 1), 35 | onAction: () => {this.eventLog(null, 'Crumb click');} 36 | }); 37 | } 38 | 39 | public pushUrl() { 40 | this.breadcrumbs.push({ 41 | content: "URL link " + (this.breadcrumbs.length + 1), 42 | url: "doc" 43 | }); 44 | } 45 | 46 | public pushRouterLink() { 47 | this.breadcrumbs.push({ 48 | content: "Router link " + (this.breadcrumbs.length + 1), 49 | routerLink: "/doc" 50 | }); 51 | } 52 | 53 | public get code(): string { 54 | const status = this.nullableAttr('status'); 55 | const progress = this.nullableAttr('progress'); 56 | 57 | let breadcrumbs: string = this.breadcrumbs.map((crumb) => { 58 | if (crumb.onAction) { 59 | return this.indent(`{'content': '${crumb.content}, 'onAction': () => eventLog(null, 'Crumb click') }'`, 2); 60 | } else { 61 | return this.indent(JSON.stringify(crumb).replace(/"/g, "'"), 2); 62 | } 63 | }).join(','); 64 | 65 | 66 | return ` 69 | `; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/app/doc/components/checkbox.component.html: -------------------------------------------------------------------------------- 1 | 2 | 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/app/doc/components/checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | 6 | @Component({ 7 | templateUrl: './checkbox.component.html' 8 | }) 9 | export class CheckboxComponent extends ComponentComponent { 10 | 11 | protected componentPath: string = 'checkbox/checkbox.component'; 12 | 13 | label:string = 'Hello world'; 14 | labelHidden:boolean = false; 15 | id:string = ''; 16 | name:string = 'hello'; 17 | checked:boolean = false; 18 | helpText:string = ''; 19 | logChange: boolean = false; 20 | logFocus: boolean = false; 21 | logBlur: boolean = false; 22 | bindedValue: boolean = false; 23 | 24 | constructor(protected service: DocService) { 25 | super(service); 26 | } 27 | 28 | public get code(): string { 29 | const label = this.nullableAttr('label'); 30 | const labelHidden = this.labelHidden ? "\n [labelHidden]=\"labelHidden\"" : ""; 31 | const helpText = this.nullableAttr('helpText'); 32 | const id = this.nullableAttr('id'); 33 | const change = this.eventAttr('change'); 34 | const focus = this.eventAttr('focus'); 35 | const blur = this.eventAttr('blur'); 36 | 37 | return ``; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/app/doc/components/choice.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |

Choice is a system component used by the checkbox component and choice list component. It's not meant to be used directly.

15 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/doc/components/choice.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | 6 | @Component({ 7 | templateUrl: './choice.component.html' 8 | }) 9 | export class ChoiceComponent extends ComponentComponent { 10 | 11 | protected componentPath: string = 'choice/choice.component'; 12 | 13 | label:string = 'Hello world'; 14 | id:string = 'SampleID123'; 15 | helpText:string = ''; 16 | error:any = ''; 17 | 18 | constructor(protected service: DocService) { 19 | super(service); 20 | } 21 | 22 | public get code(): string { 23 | const label = this.nullableAttr('label'); 24 | const id = this.nullableAttr('id'); 25 | const helpText = this.nullableAttr('helpText'); 26 | const error = this.nullableAttr('error'); 27 | 28 | 29 | return ` 30 | 31 | `; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/app/doc/components/choice.list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /src/app/doc/components/choice.list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | import { Types } from '../../library'; 6 | 7 | @Component({ 8 | templateUrl: './choice.list.component.html' 9 | }) 10 | export class ChoiceListComponent extends ComponentComponent { 11 | 12 | protected componentPath: string = 'radio.button/radio.button.component'; 13 | 14 | title:string = 'Hello world'; 15 | titleHidden:boolean = false; 16 | id:string = ''; 17 | logChange: boolean = false; 18 | name: string = 'choice'; 19 | bindedValue: any[] = []; 20 | allowMultiple: boolean = false; 21 | 22 | choices: Types.Option[] = [ 23 | 'Choice One', 24 | 'Choice Two', 25 | {label: 'Choice Three', value:3}, 26 | {label: 'Choice Four', value:{hello:'world'}}, 27 | ]; 28 | 29 | 30 | constructor(protected service: DocService) { 31 | super(service); 32 | } 33 | 34 | public get code(): string { 35 | const title = this.nullableAttr('title'); 36 | const titleHidden = this.booleanAttr("titleHidden"); 37 | const id = this.nullableAttr('id'); 38 | const name = this.nullableAttr('name'); 39 | const allowMultiple = this.booleanAttr('allowMultiple'); 40 | 41 | const choices = this.jsonAttr('[choices]', this.choices); 42 | 43 | 44 | const change = this.eventAttr('change'); 45 | const bind = this.attr('[(ngModel)]','bindedValue'); 46 | 47 | 48 | return ``; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/doc/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './badge.component'; 2 | export * from './banner.component'; 3 | export * from './breadcrumbs.component'; 4 | export * from './checkbox.component'; 5 | export * from './choice.component'; 6 | export * from './choice.list.component'; 7 | export * from './radio.button.component'; 8 | -------------------------------------------------------------------------------- /src/app/doc/components/radio.button.component.html: -------------------------------------------------------------------------------- 1 | 2 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/app/doc/components/radio.button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input} from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | import { ComponentComponent } from '../utilities/component.component'; 5 | 6 | @Component({ 7 | templateUrl: './radio.button.component.html' 8 | }) 9 | export class RadioButtonComponent extends ComponentComponent { 10 | 11 | protected componentPath: string = 'radio.button/radio.button.component'; 12 | 13 | label:string = 'Hello world'; 14 | labelHidden:boolean = false; 15 | id:string = ''; 16 | name:string = 'hello'; 17 | checked:boolean = false; 18 | helpText:string = ''; 19 | logChange: boolean = false; 20 | logFocus: boolean = false; 21 | logBlur: boolean = false; 22 | value: string = 'world'; 23 | bindedValue:any; 24 | 25 | constructor(protected service: DocService) { 26 | super(service); 27 | } 28 | 29 | public get code(): string { 30 | const label = this.nullableAttr('label'); 31 | const labelHidden = this.labelHidden ? " labelHidden" : ""; 32 | const helpText = this.nullableAttr('helpText'); 33 | const id = this.nullableAttr('id'); 34 | const name = this.nullableAttr('name'); 35 | const value = this.nullableAttr('value'); 36 | const change = this.eventAttr('change'); 37 | const focus = this.eventAttr('focus'); 38 | const blur = this.eventAttr('blur'); 39 | 40 | return ``; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/app/doc/doc.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/doc/doc.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { DocService } from './doc.service'; 3 | import { Pagination } from '../library/' 4 | 5 | @Component({ 6 | templateUrl: 'doc.component.html', 7 | }) 8 | export class DocComponent { 9 | 10 | constructor(public service:DocService) {} 11 | 12 | 13 | get title(): string { 14 | return this.service.selected == undefined ? 15 | 'List of Angular Polaris Component' : 16 | `${this.service.selected.name} component`; 17 | } 18 | 19 | get breadcrumbs(): any { 20 | let crumb = [ 21 | { 22 | content: "Home", 23 | routerLink: "/" 24 | } 25 | ]; 26 | 27 | if (this.service.selected !== undefined) { 28 | crumb.push({ 29 | content: 'Component list', 30 | routerLink: '/doc' 31 | }); 32 | } 33 | 34 | return crumb; 35 | } 36 | 37 | get pagination(): Pagination.PaginationDescriptor { 38 | const previous = this.service.previous; 39 | const next = this.service.next; 40 | 41 | let pagination: Pagination.PaginationDescriptor = {}; 42 | if (previous) { 43 | pagination.hasPrevious = true; 44 | pagination.previousRouterLink = '/doc/' + previous.path; 45 | } 46 | 47 | if (next) { 48 | pagination.hasNext = true; 49 | pagination.nextRouterLink = '/doc/' + next.path; 50 | } 51 | 52 | return pagination; 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/doc/doc.data.ts: -------------------------------------------------------------------------------- 1 | export interface PolarisComponent { 2 | name: string, 3 | path: string, 4 | docLink?: string, 5 | status: 'dev'|'beta'|'completed'|'not started', 6 | category: string, 7 | } 8 | 9 | export const docData: PolarisComponent[] = [ 10 | { 11 | name: 'Badge', 12 | path: 'badge/badge.component', 13 | status: 'completed', 14 | docLink: 'images-and-icons/badge', 15 | category: 'image/icon', 16 | }, 17 | { 18 | name: 'Banner', 19 | path: 'banner/banner.component', 20 | status: 'beta', 21 | docLink: 'feedback-indicators/banner', 22 | category: 'feedback', 23 | }, 24 | { 25 | name: 'Breadcrumbs', 26 | path: 'breadcrumbs/breadcrumbs.component', 27 | status: 'beta', 28 | category: 'system', 29 | }, 30 | { 31 | name: 'Checkbox', 32 | path: 'checkbox/checkbox.component', 33 | status: 'dev', 34 | docLink: 'forms/checkbox', 35 | category: 'form', 36 | }, 37 | { 38 | name: 'Choice', 39 | path: 'choice/choice.component', 40 | status: 'dev', 41 | category: 'system', 42 | }, 43 | { 44 | name: 'ChoiceList', 45 | path: 'choice.list/choice.list.component', 46 | status: 'dev', 47 | docLink: 'forms/choice-list', 48 | category: 'form', 49 | }, 50 | { 51 | name: 'RadioButton', 52 | path: 'radio.button/radio.button.component', 53 | status: 'dev', 54 | docLink: 'forms/radio-button', 55 | category: 'form', 56 | }, 57 | ]; 58 | 59 | // export const componentList: Function[] = docData.map((meta) => meta.component); 60 | -------------------------------------------------------------------------------- /src/app/doc/doc.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { AngularPolarisModule } from '../library'; 6 | 7 | 8 | import { DocComponent } from './doc.component'; 9 | import { DocService } from './doc.service'; 10 | import { ListComponent } from './utilities/list.component'; 11 | import { ComponentDocWrapperComponent } from './utilities/component.doc.wrapper.component'; 12 | import { CodeCardComponent } from './utilities/code.card.component'; 13 | import { MetaComponent } from './utilities/meta.component'; 14 | 15 | import * as C from './components'; 16 | import { docData } from './doc.data'; 17 | 18 | const appRoutes: Routes = [ 19 | { path: 'doc', component: DocComponent, children: [ 20 | { path: '', redirectTo: 'list', pathMatch: 'full' }, 21 | { path: 'list', component: ListComponent }, 22 | { path: 'badge/badge.component', component: C.BadgeComponent }, 23 | { path: 'banner/banner.component', component: C.BannerComponent }, 24 | { path: 'breadcrumbs/breadcrumbs.component', component: C.BreadcrumbsComponent }, 25 | { path: 'checkbox/checkbox.component', component: C.CheckboxComponent }, 26 | { path: 'choice/choice.component', component: C.ChoiceComponent }, 27 | { path: 'choice.list/choice.list.component', component: C.ChoiceListComponent }, 28 | { path: 'radio.button/radio.button.component', component: C.RadioButtonComponent }, 29 | ] 30 | } 31 | ]; 32 | 33 | @NgModule({ 34 | imports: [ 35 | CommonModule, 36 | FormsModule, 37 | RouterModule.forChild(appRoutes), 38 | AngularPolarisModule 39 | ], 40 | declarations: [ 41 | DocComponent, 42 | ComponentDocWrapperComponent, 43 | ListComponent, 44 | CodeCardComponent, 45 | MetaComponent, 46 | C.BadgeComponent, 47 | C.BannerComponent, 48 | C.BreadcrumbsComponent, 49 | C.CheckboxComponent, 50 | C.ChoiceComponent, 51 | C.ChoiceListComponent, 52 | C.RadioButtonComponent, 53 | ], 54 | exports: [ ], 55 | providers: [ 56 | DocService 57 | ], 58 | }) 59 | export class DocModule { 60 | // static forRoot(): ModuleWithProviders { 61 | // return { 62 | // ngModule: SampleModule, 63 | // providers: [SampleService] 64 | // }; 65 | // } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/doc/doc.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, NavigationEnd } from '@angular/router'; 3 | 4 | import {docData, PolarisComponent} from './doc.data' 5 | 6 | @Injectable() 7 | export class DocService { 8 | 9 | constructor(private router: Router) { 10 | this.router.events.subscribe(this.selectComponent); 11 | } 12 | 13 | private selectComponent = (routeChange: NavigationEnd) => { 14 | let matches: RegExpMatchArray = routeChange.url.match(/doc\/(.*?)\/?$/i); 15 | if (matches) { 16 | this.select(matches[1]); 17 | } else { 18 | this.selected = undefined; 19 | } 20 | } 21 | 22 | 23 | public get components(): PolarisComponent[] { 24 | return docData.filter((component: PolarisComponent) => { 25 | return this.filter == '' || component.name.match(new RegExp(this.filter, 'i')); 26 | }); 27 | } 28 | 29 | public filter: string = ''; 30 | 31 | public selected: PolarisComponent = undefined; 32 | 33 | public get previous(): PolarisComponent { 34 | const components = this.components; 35 | for (let i = 0 ; i < components.length; i++) { 36 | if (components[i] === this.selected) { 37 | return components[i-1]; 38 | } 39 | } 40 | return undefined; 41 | } 42 | 43 | public get next(): PolarisComponent { 44 | const components = this.components; 45 | for (let i = 0 ; i < components.length; i++) { 46 | if (components[i] === this.selected) { 47 | return components[i+1]; 48 | } 49 | } 50 | return undefined; 51 | } 52 | 53 | private select(componentPath: string) { 54 | this.selected = this.getByPath(componentPath); 55 | } 56 | 57 | public getByPath(path: string): PolarisComponent { 58 | if (path == 'list') { 59 | return undefined; 60 | } else { 61 | return docData.find((component: PolarisComponent) => { 62 | return component.path == path; 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/doc/utilities/code.card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewChild, ElementRef } from '@angular/core'; 2 | import * as Prism from 'prismjs'; 3 | 4 | 5 | 6 | @Component({ 7 | selector: 'codeCard', 8 | template: ` 9 |
{{ code }}
10 |
`, 11 | styles: [ 12 | "plrscard {margin-top: 2rem;}", 13 | "pre {overflow: auto;}" 14 | ] 15 | }) 16 | export class CodeCardComponent { 17 | 18 | _code = '' 19 | 20 | @Input() 21 | public get code(): string { 22 | return this._code; 23 | }; 24 | 25 | public set code(value: string) { 26 | this._code = value; 27 | this.highlight(); 28 | } 29 | 30 | @Input() public language: string = "html"; 31 | 32 | 33 | @Input() public title = 'Code'; 34 | 35 | @ViewChild('codeBlock') codeBlock: ElementRef; 36 | 37 | ngOnInit() { 38 | Prism.highlightElement(this.codeBlock.nativeElement, false); 39 | this.highlight(); 40 | } 41 | 42 | private highlight() { 43 | this.codeBlock.nativeElement.innerHTML = Prism.highlight(this.code, Prism.languages[this.language]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/doc/utilities/component.component.ts: -------------------------------------------------------------------------------- 1 | import { DocService } from '../doc.service'; 2 | import { PolarisComponent } from '../doc.data'; 3 | 4 | export abstract class ComponentComponent { 5 | 6 | public component: PolarisComponent; 7 | protected abstract componentPath; 8 | 9 | constructor(protected service: DocService) { 10 | 11 | 12 | } 13 | 14 | ngOnInit() { 15 | this.component = this.service.getByPath(this.componentPath); 16 | } 17 | 18 | /** 19 | * Build an optional attribute for the Angular code preview 20 | * @param {string} attr [description] 21 | * @return {string} [description] 22 | */ 23 | protected nullableAttr(attr: string): string { 24 | return this[attr] != "" ? this.indent(`${attr}="${this[attr]}"`) : ''; 25 | } 26 | 27 | /** 28 | * Build an optional event wire for the Angular code preview 29 | * @param {string} attr [description] 30 | * @return {string} [description] 31 | */ 32 | protected eventAttr(eventName: string): string { 33 | const eventUC = this.capitalizeFirstLetter(eventName); 34 | return this['log' + eventUC] ? this.indent(`(${eventName})="on${eventUC}($event)"`) : ''; 35 | } 36 | 37 | /** 38 | * Build an optional valueless attribute for the Angular code preview 39 | */ 40 | protected booleanAttr(attr: string): string { 41 | return this[attr] ? this.indent(attr) : ''; 42 | } 43 | 44 | /** 45 | * Build an optional valueless attribute for the Angular code preview 46 | */ 47 | protected jsonAttr(attr: string, obj: any): string { 48 | return this.attr(attr, JSON.stringify(obj, null, " ").replace(/\"/g, '\'')); 49 | } 50 | 51 | /** 52 | * Build an attribute for the Angular code proview 53 | */ 54 | protected attr(attr: string, value:string): string { 55 | return this.indent(`${attr}="${value}"`); 56 | } 57 | 58 | private capitalizeFirstLetter(string) { 59 | return string.charAt(0).toUpperCase() + string.slice(1); 60 | } 61 | 62 | protected indent(str: string, count:number = 1): string { 63 | let indented: string = "\n"; 64 | while (count > 0) { 65 | indented += " "; 66 | count--; 67 | } 68 | indented += str; 69 | return indented; 70 | } 71 | 72 | eventLog(event: any, name: string = ''):void { 73 | if (name) { 74 | console.log(`Event: ${name}`); 75 | } 76 | 77 | console.dir(event); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app/doc/utilities/component.doc.wrapper.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/app/doc/utilities/component.doc.wrapper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, ContentChildren, ElementRef, QueryList} from '@angular/core'; 2 | import { PolarisComponent } from '../doc.data'; 3 | 4 | 5 | 6 | @Component({ 7 | selector: 'componentDocWrapper', 8 | templateUrl: 'component.doc.wrapper.component.html' 9 | }) 10 | export class ComponentDocWrapperComponent { 11 | 12 | @Input() bindedValue: any; 13 | 14 | @Input() code: string; 15 | 16 | @Input() component: PolarisComponent; 17 | 18 | @Input() showLog: boolean = false; 19 | 20 | get bindedValueJson(): string { 21 | return JSON.stringify(this.bindedValue, null, " "); 22 | } 23 | 24 | clearConsole = {content: 'Clear console', onAction: console.clear}; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/doc/utilities/list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/doc/utilities/list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { DocService } from '../doc.service'; 3 | import { PolarisComponent } from '../doc.data'; 4 | 5 | const STATUSES = { 6 | 'dev': 'attention', 7 | 'beta': 'warning', 8 | 'completed': 'success', 9 | 'not started': 'info', 10 | }; 11 | 12 | const PROGRESSES = { 13 | 'dev': 'partiallyComplete', 14 | 'beta': 'partiallyComplete', 15 | 'completed': 'complete', 16 | 'not started': 'incomplete', 17 | }; 18 | 19 | @Component({ 20 | templateUrl: 'list.component.html', 21 | }) 22 | export class ListComponent { 23 | 24 | constructor(public service: DocService) {} 25 | 26 | public badge(component: PolarisComponent) { 27 | return [ 28 | { 29 | content: component.status, 30 | status: STATUSES[component.status], 31 | progress: PROGRESSES[component.status] 32 | }, 33 | { 34 | content: component.category, 35 | }, 36 | ]; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/app/doc/utilities/meta.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { PolarisComponent } from '../doc.data'; 3 | import { environment } from '../../../environments/environment'; 4 | 5 | @Component({ 6 | selector: 'metaComponent', 7 | template: ` 8 | 12 | `, 13 | styles: ["plrscard {margin-top: 2rem;}"] 14 | }) 15 | export class MetaComponent { 16 | @Input() component: PolarisComponent; 17 | 18 | github = environment.repo; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Angular Polaris is a conversion of the Shopify Polaris project from React to Angular.

6 |

The Angular Polaris library tries to implement the Shopify Polaris API as-is as much as possible. Exceptions are made when the Shopify Polaris breaks common Angular convention. Typical adaptation include:

7 | 8 |
    9 |
  • Prefixing all component with plrs.
  • 10 |
  • Component event use the Angular @Output notation with EventEmitter. (e.g.: <plrsCheckbox (change)="onchange($event)"></plrsCheckbox>)
  • 11 |
  • Self-closing tags are not used because Angular doesn't support that syntax.
  • 12 |
13 | 14 |
15 | 16 |

The purpose of this project is to illustrate the component that are available and how to use them in your angular project.

17 |
18 | 19 | 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/app/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './home.component.html', 5 | }) 6 | export class HomeComponent { 7 | 8 | public action = {content: 'Browse Documentation', routerLink: '/doc'}; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/library/AngularPolarisModule.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | import { BadgeComponent } from './badge/badge.component'; 7 | import { BannerComponent } from './banner/banner.component'; 8 | 9 | import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component'; 10 | 11 | import { ButtonComponent } from './button/button.component'; 12 | import { ButtonGroupComponent } from './button/button.group.component'; 13 | 14 | import { CardComponent } from './card/card.component'; 15 | 16 | import { CardSectionComponent } from './card/card.section.component'; 17 | 18 | import { CheckboxComponent } from './checkbox/checkbox.component'; 19 | import { ChoiceComponent } from './choice/choice.component'; 20 | import { ChoiceListComponent } from './choice.list/choice.list.component'; 21 | 22 | import { HeaderComponent } from './card/header.component'; 23 | 24 | import { IconComponent } from './icon/icon.component'; 25 | 26 | import { LabelComponent } from './label/label.component'; 27 | import { LabelledComponent } from './labelled/labelled.component'; 28 | 29 | import { LayoutComponent, SectionedLayoutComponent } from './layout/layout.component'; 30 | import { LayoutAnnotatedSectionComponent } from './layout/layout.annotated.section.component'; 31 | import { SectionComponent } from './layout/section.component'; 32 | 33 | import { PageComponent } from './page/page.component'; 34 | import { PageHeaderComponent } from './page/page.header.component'; 35 | 36 | import { PaginationComponent } from './pagination/pagination.component'; 37 | 38 | import { RadioButtonComponent } from './radio.button'; 39 | 40 | import { ResourceListComponent } from './resource.list/resource.list.component'; 41 | import { ResourceListItemComponent } from './resource.list/resource.list.item.component'; 42 | 43 | import { SelectComponent } from './select/select.component'; 44 | 45 | import { StackComponent } from './stack/stack.component'; 46 | import { StackItemComponent } from './stack/stack.item.component'; 47 | 48 | import { TextFieldComponent } from './text.field/text.field.component'; 49 | import { SpinnerComponent } from './text.field/spinner.component'; 50 | import { ResizerComponent } from './text.field/resizer.component'; 51 | 52 | import { TemplateOrStringComponent } from './utilities/template.or.string.component'; 53 | 54 | import { ThumbnailComponent } from './thumbnail/thumbnail.component'; 55 | 56 | import { VisuallyHiddenComponent } from './visually.hidden/visually.hidden.component'; 57 | 58 | import { UnstyledLinkComponent } from './unstyled.link/unstyled.link.component'; 59 | 60 | import { WysiwygComponent } from './wysiwyg/wysiwyg.component'; 61 | 62 | export * from './types'; 63 | 64 | const components = [ 65 | BadgeComponent, 66 | BannerComponent, 67 | BreadcrumbsComponent, 68 | ButtonComponent, 69 | ButtonGroupComponent, 70 | CardComponent, 71 | CardSectionComponent, 72 | CheckboxComponent, 73 | ChoiceComponent, 74 | ChoiceListComponent, 75 | HeaderComponent, 76 | IconComponent, 77 | LabelComponent, 78 | LabelledComponent, 79 | LayoutAnnotatedSectionComponent, 80 | LayoutComponent, 81 | PageComponent, 82 | PageHeaderComponent, 83 | PaginationComponent, 84 | RadioButtonComponent, 85 | ResizerComponent, 86 | ResourceListComponent, 87 | ResourceListItemComponent, 88 | SectionComponent, 89 | SectionedLayoutComponent, 90 | SelectComponent, 91 | SpinnerComponent, 92 | StackComponent, 93 | StackItemComponent, 94 | TemplateOrStringComponent, 95 | TextFieldComponent, 96 | ThumbnailComponent, 97 | UnstyledLinkComponent, 98 | VisuallyHiddenComponent, 99 | WysiwygComponent, 100 | ]; 101 | 102 | @NgModule({ 103 | imports: [ 104 | CommonModule, 105 | RouterModule, 106 | FormsModule, 107 | ], 108 | declarations: components, 109 | exports: components 110 | }) 111 | export class AngularPolarisModule { 112 | // static forRoot(): ModuleWithProviders { 113 | // return { 114 | // ngModule: AngularPolarisModule, 115 | // providers: [] 116 | // }; 117 | // } 118 | } 119 | -------------------------------------------------------------------------------- /src/app/library/badge/badge.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ progressLabel }} 3 | 4 | 5 | {{ statusLabel }} 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/library/badge/badge.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, TemplateRef } from '@angular/core'; 2 | 3 | export type Status = '' | 'success' | 'info' | 'attention' | 'warning'; 4 | export type Progress = '' | 'incomplete' | 'partiallyComplete' | 'complete'; 5 | 6 | const PROGRESS_LABELS = { 7 | incomplete: 'Incomplete', 8 | partiallyComplete: 'Partially complete', 9 | complete: 'Complete', 10 | }; 11 | 12 | const STATUS_LABELS = { 13 | info: 'Info', 14 | success: 'Success', 15 | warning: 'Warning', 16 | attention: 'Attention', 17 | }; 18 | 19 | /** 20 | * Component to display a Shopify layout 21 | */ 22 | @Component({ 23 | selector: 'plrsBadge', 24 | templateUrl: './badge.component.html', 25 | host: { 26 | '[class.Polaris-Badge]': 'true', 27 | '[class.Polaris-Badge--statusSuccess]': 'status == "success"', 28 | '[class.Polaris-Badge--statusInfo]': 'status == "info"', 29 | '[class.Polaris-Badge--statusWarning]': 'status == "warning"', 30 | '[class.Polaris-Badge--statusAttention]': 'status == "attention"', 31 | '[class.Polaris-Badge--progressIncomplete]': 'progress == "incomplete"', 32 | '[class.Polaris-Badge--progressPartiallyComplete]': 'progress == "partiallyComplete"', 33 | '[class.Polaris-Badge--progressComplete]': 'progress == "complete"', 34 | }, 35 | }) 36 | export class BadgeComponent implements OnInit { 37 | 38 | ngOnInit() { } 39 | 40 | /** 41 | * Title content for the card. 42 | */ 43 | @Input() public status: Status = ''; 44 | 45 | @Input() public progress: Status = ''; 46 | 47 | public get progressLabel(): string { 48 | return PROGRESS_LABELS[this.progress] ? PROGRESS_LABELS[this.progress] : ''; 49 | } 50 | 51 | public get statusLabel(): string { 52 | return STATUS_LABELS[this.status] ? STATUS_LABELS[this.status] : ''; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/library/banner/banner.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |

{{ title }}

8 |
9 |
10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/library/banner/banner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, OnInit, TemplateRef } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 4 | 5 | const getUniqueID = createUniqueIDFactory('Banner'); 6 | 7 | /** 8 | * Component to display a Shopify layout 9 | */ 10 | @Component({ 11 | selector: 'plrsBanner', 12 | templateUrl: './banner.component.html', 13 | host: { 14 | '[class.Polaris-Banner]': 'true', 15 | '[class.Polaris-Banner--statusSuccess]': 'status == "success"', 16 | '[class.Polaris-Banner--statusInfo]': 'status == "info"', 17 | '[class.Polaris-Banner--statusWarning]': 'status == "warning"', 18 | '[class.Polaris-Banner--statusCritical]': 'status == "critical"', 19 | '[class.Polaris-Banner--hasDismiss]': 'dimissible', 20 | '[attr.role]': '"banner " + status', 21 | '[attr.aria-labelledby]': 'id + "Heading"', 22 | '[attr.aria-describedby]': 'id + "Content"', 23 | '[attr.tabindex]': '0', 24 | }, 25 | }) 26 | export class BannerComponent implements OnInit { 27 | 28 | ngOnInit() { } 29 | 30 | /** 31 | * Title content for the card. 32 | */ 33 | @Input() title: string = ''; 34 | 35 | public id = getUniqueID(); 36 | 37 | private _icon : string; 38 | 39 | /** 40 | * Icon to display in the banner. 41 | */ 42 | @Input() public get icon(): string { 43 | if (this._icon) { 44 | return this._icon; 45 | } else { 46 | switch (this.status) { 47 | case 'success': 48 | return 'circle-check-mark'; 49 | case 'info': 50 | return 'flag'; 51 | case 'warning': 52 | return 'circle-alert'; 53 | case 'critical': 54 | return 'circle-barred'; 55 | default: 56 | return 'confetti'; 57 | } 58 | } 59 | }; 60 | public set icon(value: string) { 61 | this._icon = value; 62 | } 63 | 64 | /** 65 | * Sets the status of the banner. 66 | */ 67 | @Input() status: '' | 'success' | 'info' | 'warning' | 'critical' = ''; 68 | 69 | /** 70 | * Action for banner. 71 | */ 72 | @Input() action: AngularComplexAction; 73 | 74 | /** 75 | * Displays a secondary action 76 | */ 77 | @Input() secondaryAction: AngularComplexAction; 78 | 79 | @Output() dismiss: EventEmitter = new EventEmitter(); 80 | 81 | public get dimissible(): boolean { 82 | return this.dismiss.observers.length > 0; 83 | } 84 | 85 | public get iconColor(): string { 86 | switch (this.status) { 87 | case 'success': 88 | return 'greenDark'; 89 | case 'info': 90 | return 'tealDark'; 91 | case 'warning': 92 | return 'yellowDark'; 93 | case 'critical': 94 | return 'redDark'; 95 | default: 96 | return 'ink'; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/app/library/banner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './banner.component' 2 | -------------------------------------------------------------------------------- /src/app/library/breadcrumbs/breadcrumbs.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/library/breadcrumbs/breadcrumbs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, QueryList, ContentChildren, AfterContentInit } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | @Component({ 5 | selector: 'plrsBreadcrumbs', 6 | templateUrl: 'breadcrumbs.component.html', 7 | styles: [':host {display: block;}'], 8 | host: { 9 | '[attr.role]': '"navigation"', 10 | }, 11 | }) 12 | export class BreadcrumbsComponent implements OnInit, AfterContentInit { 13 | 14 | 15 | @Input() public breadcrumbs:AngularComplexAction[]; 16 | 17 | ngOnInit() { } 18 | 19 | ngAfterContentInit() { 20 | 21 | } 22 | 23 | public get action(): AngularComplexAction { 24 | if (this.breadcrumbs && this.breadcrumbs.length > 0) { 25 | return this.breadcrumbs[this.breadcrumbs.length - 1]; 26 | } 27 | return null; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/library/button/button.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{ children }} 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ children }} 34 | 35 | 36 | 37 | 38 | 57 | 58 | -------------------------------------------------------------------------------- /src/app/library/button/button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsButton', 9 | templateUrl: 'button.component.html', 10 | host: { 11 | '[class.Polaris-ButtonGroup__Item]': 'inGroup', 12 | '[class.Polaris-Button--fullWidth]': 'fullWidth !== false', 13 | }, 14 | styles: [':host {display: inline-block;}'] 15 | }) 16 | export class ButtonComponent implements OnInit { 17 | 18 | ngOnInit() { } 19 | 20 | 21 | @Input() set fromAction(action:AngularComplexAction) { 22 | this.children = action.content; 23 | this.accessibilityLabel = action.accessibilityLabel; 24 | this.plain = action.plain ? true : false; 25 | 26 | // Wire links if need be 27 | if (action.routerLink) { 28 | this.routerLink = action.routerLink; 29 | } else if (action.url) { 30 | this.url = action.url; 31 | } 32 | 33 | // Handle subsciprion to the click event 34 | this.action = new EventEmitter(); 35 | if (action.onAction) { 36 | this.action.subscribe(action.onAction); 37 | } 38 | 39 | } 40 | 41 | /** 42 | * The content to display inside the button. 43 | */ 44 | @Input() children:string; 45 | 46 | 47 | @Input() accessibilityLabel:string; 48 | 49 | /** 50 | * URL to link to 51 | */ 52 | @Input() url:string; 53 | 54 | 55 | /** 56 | * Make this button an angular router link 57 | */ 58 | @Input() routerLink:string; 59 | 60 | @Output() action: EventEmitter = new EventEmitter(); 61 | 62 | /** 63 | * Display as primary button 64 | */ 65 | @Input() primary: boolean = false; 66 | 67 | /** 68 | * Display as destructive button 69 | */ 70 | @Input() destructive: boolean = false; 71 | 72 | /** 73 | * Display as disabled button. 74 | */ 75 | @Input() disabled: boolean = false; 76 | 77 | /** 78 | * Display as disabled button. 79 | */ 80 | @Input() plain: boolean = false; 81 | 82 | @Input() inGroup: boolean = false; 83 | 84 | @Input() outline: boolean = false; 85 | 86 | @Input() external: boolean = false; 87 | 88 | @Input() iconOnly: boolean = false; 89 | 90 | @Input() fullWidth: boolean = false; 91 | 92 | @Input() icon: string; 93 | 94 | 95 | /** 96 | * Apply classes to the inner button. 97 | */ 98 | @Input() innerClass: string = ''; 99 | 100 | ngClass() { 101 | return { 102 | 'Polaris-Button--primary': this.primary !== false, 103 | 'Polaris-Button--destructive': this.destructive !== false, 104 | 'Polaris-Button--disabled': this.disabled !== false, 105 | 'Polaris-Button--plain': this.plain !== false, 106 | 'Polaris-Button--outline': this.outline !== false, 107 | 'Polaris-Button--iconOnly': this.iconOnly !== false, 108 | 'Polaris-Button--fullWidth': this.fullWidth !== false, 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/app/library/button/button.group.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/library/button/button.group.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, QueryList, ContentChildren, AfterContentInit } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | import { ButtonComponent } from './button.component'; 4 | 5 | /** 6 | * Component to display a Shopify layout 7 | */ 8 | @Component({ 9 | selector: 'plrsButtonGroup', 10 | templateUrl: 'button.group.component.html', 11 | host: { 12 | '[class.Polaris-ButtonGroup]': 'true', 13 | '[class.Polaris-Card--segmented]': 'segmented !== false' 14 | }, 15 | }) 16 | export class ButtonGroupComponent implements OnInit, AfterContentInit { 17 | 18 | ngOnInit() { } 19 | 20 | ngAfterContentInit() { 21 | this.buttons.toArray().forEach((button: ButtonComponent) => { 22 | button.inGroup = true; 23 | }); 24 | } 25 | 26 | /** 27 | * Join buttons as segmented group 28 | */ 29 | @Input() segmented:boolean; 30 | 31 | @ContentChildren(ButtonComponent) buttons: QueryList; 32 | } 33 | -------------------------------------------------------------------------------- /src/app/library/card/card.component.html: -------------------------------------------------------------------------------- 1 | {{ title }} 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/library/card/card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsCard', 9 | templateUrl: './card.component.html', 10 | host: { 11 | '[class.Polaris-Card]': 'true', 12 | '[class.Polaris-Card--subdued]': 'subdued !== false' 13 | }, 14 | styles: [':host {display: block;}'] 15 | }) 16 | export class CardComponent implements OnInit { 17 | 18 | /** 19 | * Title content for the card. 20 | */ 21 | @Input() title: string = ''; 22 | 23 | /** 24 | * A less prominent card. 25 | */ 26 | @Input('subdued') subdued: boolean = false; 27 | 28 | /** 29 | * Auto wrap content in section. 30 | */ 31 | @Input('sectioned') sectioned: boolean = false; 32 | 33 | /** 34 | * Primary action in the card footer. 35 | */ 36 | @Input() primaryFooterAction: AngularComplexAction; 37 | 38 | /** 39 | * Secondary action in the card footer. 40 | */ 41 | @Input() secondaryFooterAction: AngularComplexAction; 42 | 43 | /** 44 | * Card header actions. 45 | */ 46 | @Input() actions: AngularComplexAction[]; 47 | 48 | ngOnInit() { } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/app/library/card/card.section.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsCardSection', 8 | template: ``, 9 | host: { 10 | '[class.Polaris-Card__Section]': 'true' 11 | }, 12 | styles: [':host {display: block;}'] 13 | }) 14 | export class CardSectionComponent implements OnInit { 15 | 16 | ngOnInit() { } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/app/library/card/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | -------------------------------------------------------------------------------- /src/app/library/card/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsHeader', 9 | templateUrl: './header.component.html', 10 | host: { 11 | '[class.Polaris-Card__Header]': 'true', 12 | }, 13 | styles: [':host {display: block;}'] 14 | }) 15 | export class HeaderComponent implements OnInit { 16 | 17 | /** 18 | * Title content for the card. 19 | */ 20 | @Input() title: string = ''; 21 | 22 | 23 | /** 24 | * Card header actions. 25 | */ 26 | @Input() actions: AngularComplexAction[]; 27 | 28 | ngOnInit() { } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/library/checkbox/checkbox.component.html: -------------------------------------------------------------------------------- 1 | 7 |
8 | 16 |
17 |
18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/library/checkbox/checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, forwardRef, Optional, Inject, TemplateRef, ViewChild } from '@angular/core'; 2 | import { NgModel, DefaultValueAccessor, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; 3 | import { AngularComplexAction } from '../types'; 4 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 5 | import { ElementBase} from '../form/element.base'; 6 | 7 | 8 | const getUniqueID = createUniqueIDFactory('Checkbox'); 9 | 10 | export type Option = string | { 11 | value: string, 12 | label: string, 13 | }; 14 | 15 | /** 16 | * Component to display a Shopify layout 17 | */ 18 | @Component({ 19 | selector: 'plrsCheckbox', 20 | templateUrl: 'checkbox.component.html', 21 | providers: [ 22 | {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CheckboxComponent), multi: true} 23 | ], 24 | host: { 25 | } 26 | }) 27 | export class CheckboxComponent extends ElementBase implements OnInit { 28 | 29 | ngOnInit() { } 30 | 31 | constructor( 32 | @Optional() @Inject(NG_VALIDATORS) validators: Array, 33 | @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, 34 | ) { 35 | super(validators, asyncValidators); 36 | } 37 | 38 | @Input() options: Option[] = []; 39 | 40 | /** 41 | * Hint text to display 42 | */ 43 | @Input() placeholder: string = ""; 44 | 45 | // groups?: (Group | Option)[], 46 | /** 47 | * Additional hint text to display. 48 | */ 49 | @Input() helpText: string; 50 | @Input() label: string; 51 | @Input() labelHidden: boolean; 52 | @Input() disabled: boolean = false; 53 | @Input() checked: boolean = false; 54 | @Input() error: Error; 55 | @Input() name: string; 56 | 57 | private _id = getUniqueID(); 58 | 59 | @Input() 60 | get id(): string {return this._id;} 61 | set id(value:string) { this._id = value ? value : this._id;} 62 | 63 | @Input() step: number; 64 | @Input() value: boolean; 65 | 66 | @Output() focus: EventEmitter = new EventEmitter(); 67 | @Output() blur: EventEmitter = new EventEmitter(); 68 | 69 | 70 | @ViewChild(NgModel) model: NgModel; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/app/library/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './checkbox.component' 2 | -------------------------------------------------------------------------------- /src/app/library/choice.list/choice.list.component.html: -------------------------------------------------------------------------------- 1 | {{ title }} 2 |
    3 |
  • 4 | 10 | 17 |
  • 18 |
19 | -------------------------------------------------------------------------------- /src/app/library/choice.list/choice.list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, forwardRef, Optional, Inject, TemplateRef, ViewChild } from '@angular/core'; 2 | import { NgModel, DefaultValueAccessor, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; 3 | import { AngularComplexAction } from '../types'; 4 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 5 | import { ValueAccessorBase } from '../form/value.accessor'; 6 | import { Option } from '../types'; 7 | 8 | const getUniqueID = createUniqueIDFactory('ChoiceList'); 9 | 10 | /** 11 | * Component to display a Shopify layout 12 | */ 13 | @Component({ 14 | selector: 'plrsChoiceList', 15 | templateUrl: 'choice.list.component.html', 16 | host: { 17 | '[class.Polaris-ChoiceList]': 'true', 18 | '[class.Polaris-ChoiceList--titleHidden]': 'titleHidden !== false', 19 | '[attr.role]': 'allowMultiple === false ? "radiogroup" : "group"', 20 | }, 21 | providers: [ 22 | {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ChoiceListComponent), multi: true} 23 | ], 24 | }) 25 | export class ChoiceListComponent extends ValueAccessorBase { 26 | 27 | // constructor( 28 | // @Optional() @Inject(NG_VALIDATORS) validators: Array, 29 | // @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, 30 | // ) { 31 | // super(validators, asyncValidators); 32 | // } 33 | 34 | /** 35 | * List of possible options 36 | */ 37 | @Input() choices: Option[] = []; 38 | 39 | /** 40 | * Title for the fieldset 41 | */ 42 | @Input() title: string; 43 | 44 | /** 45 | * Whatever to hide the title of the Choice list 46 | */ 47 | @Input() titleHidden: boolean = false; 48 | 49 | @Input() error: Error; 50 | 51 | @Input() id = getUniqueID(); 52 | 53 | private _name: string; 54 | 55 | /** 56 | * Name applied to the underlying inputs 57 | */ 58 | @Input() 59 | get name(): string { 60 | if (this._name) { 61 | return this._name; 62 | } else { 63 | // If we haven't receive an appropriate name, default to using the component ID. 64 | return this.id; 65 | } 66 | }; 67 | set name(value: string) { 68 | this._name = value; 69 | } 70 | 71 | /** 72 | * Whatever multiple entries can be selected at once. 73 | */ 74 | @Input() allowMultiple: boolean = false; 75 | 76 | /** 77 | * Require at least one entry to be selected. 78 | */ 79 | @Input() required: boolean = false; 80 | 81 | @Input('selected') value: any[] = []; 82 | 83 | /** 84 | * Utility Method for determining if the provide choice should be checked. 85 | * @param {Option} choice 86 | * @return {boolean} 87 | */ 88 | isChecked(choice: Option): boolean { 89 | return this.value.indexOf(this.choiceValue(choice)) > -1; 90 | } 91 | 92 | /** 93 | * Utility function for extracting the value from the provide choice. 94 | * @param {Option} choice 95 | * @return {any} 96 | */ 97 | choiceValue(choice: Option): any { 98 | return choice['value'] === undefined ? choice : choice['value']; 99 | } 100 | 101 | /** 102 | * Utility function for extracting the label from the choice. 103 | * @param {Option} choice 104 | * @return {string} 105 | */ 106 | choiceLabel(choice: Option): string { 107 | return choice['label'] === undefined ? choice : choice['label']; 108 | } 109 | 110 | /** 111 | * Handle the checking/unchecking of the checkboxes. 112 | * @param {Option} choice 113 | * @param {Event} event 114 | */ 115 | checkboxChange(choice: Option, event: Event) { 116 | if (event.srcElement['checked']) { 117 | // If the checkbox has been checked, make sure the choice is in our result array. 118 | this.add(choice); 119 | } else { 120 | // If the checkbox is not checked, remove the choice 121 | this.remove(choice); 122 | } 123 | } 124 | 125 | /** 126 | * Remove an entry from the value array. 127 | * @param {Option} choice [description] 128 | */ 129 | private remove(choice: Option) { 130 | const idx = this.value.indexOf(this.choiceValue(choice)); 131 | 132 | if (idx > -1) { 133 | console.log('removing ' + idx); 134 | this.value.splice(idx,1); 135 | } 136 | } 137 | 138 | /** 139 | * Add an entry to our value array. 140 | * @param {Option} choice [description] 141 | */ 142 | private add(choice: Option) { 143 | const idx = this.value.indexOf(this.choiceValue(choice)); 144 | 145 | if (idx == -1) { 146 | this.value.push(this.choiceValue(choice)); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/app/library/choice.list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './choice.list.component'; 2 | -------------------------------------------------------------------------------- /src/app/library/choice/choice.component.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/app/library/choice/choice.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsChoice', 9 | templateUrl: './choice.component.html', 10 | host: { }, 11 | styles: [':host {display: block;}'] 12 | }) 13 | export class ChoiceComponent { 14 | 15 | /** 16 | * ID of the input the label will be linked to 17 | */ 18 | @Input() id: string = ''; 19 | 20 | /** 21 | * Title content for the card. 22 | */ 23 | @Input() label: string = ''; 24 | 25 | @Input() labelHidden: boolean; 26 | 27 | @Input() error: Error; 28 | 29 | @Input() helpText: string; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/library/choice/index.ts: -------------------------------------------------------------------------------- 1 | export { ChoiceComponent } from './choice.component'; 2 | -------------------------------------------------------------------------------- /src/app/library/form/animations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnimationEntryMetadata, 3 | animate, 4 | state, 5 | style, 6 | transition, 7 | trigger, 8 | } from '@angular/core'; 9 | 10 | export const animations: Array = [ 11 | trigger('flyInOut', [ 12 | state('in', style({transform: 'translateY(0)'})), 13 | transition('void => *', [ 14 | style({transform: 'translateY(-100%)'}), 15 | animate(100) 16 | ]), 17 | state('out', style({transform: 'translateY(100%)'})), 18 | transition('* => void', [ 19 | animate(100, style({transform: 'translateY(100%)'})) 20 | ]) 21 | ]) 22 | ]; -------------------------------------------------------------------------------- /src/app/library/form/element.base.ts: -------------------------------------------------------------------------------- 1 | import {NgModel} from '@angular/forms'; 2 | 3 | import {Observable} from 'rxjs'; 4 | 5 | import {ValueAccessorBase} from './value.accessor'; 6 | 7 | import { 8 | AsyncValidatorArray, 9 | ValidatorArray, 10 | ValidationResult, 11 | message, 12 | validate, 13 | } from './validate'; 14 | 15 | export abstract class ElementBase extends ValueAccessorBase { 16 | protected abstract model: NgModel; 17 | 18 | constructor( 19 | private validators: ValidatorArray, 20 | private asyncValidators: AsyncValidatorArray, 21 | ) { 22 | super(); 23 | } 24 | 25 | protected validate(): Observable { 26 | return validate 27 | (this.validators, this.asyncValidators) 28 | (this.model.control); 29 | } 30 | 31 | public get invalid(): Observable { 32 | return this.validate().map(v => Object.keys(v || {}).length > 0); 33 | } 34 | 35 | public get failures(): Observable> { 36 | 37 | return this.validate().map(v => Object.keys(v).map(k => message(v, k))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/library/form/validate.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractControl, 3 | AsyncValidatorFn, 4 | Validator, 5 | Validators, 6 | ValidatorFn, 7 | } from '@angular/forms'; 8 | 9 | import {Observable} from 'rxjs'; 10 | 11 | export type ValidationResult = {[validator: string]: string | boolean}; 12 | 13 | export type AsyncValidatorArray = Array; 14 | 15 | export type ValidatorArray = Array; 16 | 17 | const normalizeValidator = 18 | (validator: Validator | ValidatorFn): ValidatorFn | AsyncValidatorFn => { 19 | const func = (validator as Validator).validate.bind(validator); 20 | if (typeof func === 'function') { 21 | return (c: AbstractControl) => func(c); 22 | } else { 23 | return validator; 24 | } 25 | }; 26 | 27 | export const composeValidators = 28 | (validators: ValidatorArray): AsyncValidatorFn | ValidatorFn => { 29 | if (validators == null || validators.length === 0) { 30 | return null; 31 | } 32 | return Validators.compose(validators.map(normalizeValidator)); 33 | }; 34 | 35 | export const validate = 36 | (validators: ValidatorArray, asyncValidators: AsyncValidatorArray) => { 37 | return (control: AbstractControl) => { 38 | const synchronousValid = () => composeValidators(validators)(control); 39 | 40 | if (asyncValidators) { 41 | const asyncValidator = composeValidators(asyncValidators); 42 | 43 | return asyncValidator(control).map(v => { 44 | const secondary = synchronousValid(); 45 | if (secondary || v) { // compose async and sync validator results 46 | return Object.assign({}, secondary, v); 47 | } 48 | }); 49 | } 50 | 51 | if (validators) { 52 | return Observable.of(synchronousValid()); 53 | } 54 | 55 | return Observable.of(null); 56 | }; 57 | }; 58 | 59 | export const message = (validator: ValidationResult, key: string): string => { 60 | switch (key) { 61 | case 'required': 62 | return 'Please enter a value'; 63 | case 'pattern': 64 | return 'Value does not match required pattern'; 65 | case 'minlength': 66 | return 'Value must be N characters'; 67 | case 'maxlength': 68 | return 'Value must be a maximum of N characters'; 69 | } 70 | 71 | switch (typeof validator[key]) { 72 | case 'string': 73 | return validator[key]; 74 | default: 75 | return `Validation failed: ${key}`; 76 | } 77 | }; -------------------------------------------------------------------------------- /src/app/library/form/value.accessor.ts: -------------------------------------------------------------------------------- 1 | import {ControlValueAccessor} from '@angular/forms'; 2 | 3 | export abstract class ValueAccessorBase implements ControlValueAccessor { 4 | private innerValue: T; 5 | 6 | private changed = new Array<(value: T) => void>(); 7 | private touched = new Array<() => void>(); 8 | 9 | get value(): T { 10 | return this.innerValue; 11 | } 12 | 13 | set value(value: T) { 14 | if (this.innerValue !== value) { 15 | this.innerValue = value; 16 | this.changed.forEach(f => f(value)); 17 | } 18 | } 19 | 20 | writeValue(value: T) { 21 | this.innerValue = value; 22 | } 23 | 24 | registerOnChange(fn: (value: T) => void) { 25 | this.changed.push(fn); 26 | } 27 | 28 | registerOnTouched(fn: () => void) { 29 | this.touched.push(fn); 30 | } 31 | 32 | touch() { 33 | this.touched.forEach(f => f()); 34 | } 35 | } -------------------------------------------------------------------------------- /src/app/library/icon/icon.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/app/library/icon/icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, ElementRef} from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | import { SVGSource } from '@shopify/images'; 4 | 5 | /** 6 | * Component to display a Shopify layout 7 | */ 8 | @Component({ 9 | selector: 'plrsIcon', 10 | templateUrl: 'icon.component.html', 11 | host: { 12 | '[class.Polaris-Icon]': 'true', 13 | '[class.Polaris-Icon--hasBackdrop]': 'backdrop !== false', 14 | } 15 | }) 16 | export class IconComponent implements OnInit { 17 | 18 | ngOnInit() { } 19 | 20 | @Input() public source: string; 21 | 22 | private _color: string = ''; 23 | 24 | @Input() 25 | public get color(): string { 26 | return this._color; 27 | } 28 | public set color(value: string) { 29 | if (this._color != '') { 30 | this.el.nativeElement.classList.remove('Polaris-Icon--color' + this.capitalizeFirstLetter(this._color)) 31 | } 32 | 33 | this._color = value; 34 | if (this._color != '') { 35 | this.el.nativeElement.classList.add('Polaris-Icon--color' + this.capitalizeFirstLetter(this._color)) 36 | } 37 | }; 38 | 39 | private capitalizeFirstLetter(string) { 40 | return string.charAt(0).toUpperCase() + string.slice(1); 41 | } 42 | 43 | 44 | @Input() public backdrop : boolean = false; 45 | @Input() public accessibilityLabel: string; 46 | 47 | constructor(private el: ElementRef) { } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/app/library/index.ts: -------------------------------------------------------------------------------- 1 | export { AngularPolarisModule } from './AngularPolarisModule'; 2 | 3 | import * as Banner from './banner'; 4 | import * as Checkbox from './checkbox'; 5 | import * as Pagination from './pagination'; 6 | import * as RadioButton from './radio.button'; 7 | import * as Types from './types'; 8 | export { Banner, Checkbox, Pagination, RadioButton, Types }; 9 | -------------------------------------------------------------------------------- /src/app/library/label/label.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/library/label/label.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsLabel', 9 | templateUrl: 'label.component.html', 10 | host: { 11 | '[class.Polaris-Label]': 'true', 12 | } 13 | }) 14 | export class LabelComponent implements OnInit { 15 | 16 | @Output() public click = new EventEmitter(); 17 | 18 | ngOnInit() { } 19 | 20 | 21 | /** 22 | * The content to display inside the button. 23 | */ 24 | @Input() children:string; 25 | @Input() id:string; 26 | 27 | @Input() action: AngularComplexAction; 28 | @Input() hidden: boolean; 29 | 30 | public get labelID() { 31 | return `${this.id}Label`; 32 | } 33 | 34 | public onClick(event: any) { 35 | this.click.emit(event); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/library/labelled/labelled.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{ label }} 3 |
4 | 5 |
6 |
7 | 8 |
9 | 10 | {{ error }} 11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/app/library/labelled/labelled.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsLabelled', 9 | templateUrl: 'labelled.component.html', 10 | host: { 11 | '[class.Polaris-Labelled]': 'true', 12 | }, 13 | styles: [':host {display: block;}'] 14 | }) 15 | export class LabelledComponent implements OnInit { 16 | 17 | ngOnInit() { } 18 | 19 | 20 | @Input() id: string; 21 | @Input() label: string; 22 | @Input() error: any; 23 | @Input() action: AngularComplexAction; 24 | @Input() helpText: string; 25 | @Input() children: string; 26 | @Input() labelHidden: boolean; 27 | 28 | @Output() public click = new EventEmitter(); 29 | public onClick(event: any) { 30 | this.click.emit(event); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/library/layout/layout.annotated.section.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

{{ title }}

6 |

{{ description }}

7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/library/layout/layout.annotated.section.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsLayoutAnnotatedSection', 8 | templateUrl: 'layout.annotated.section.component.html', 9 | host: { 10 | '[class.Polaris-Layout__AnnotatedSection]': 'true' 11 | } 12 | }) 13 | export class LayoutAnnotatedSectionComponent implements OnInit { 14 | 15 | @Input() title: string; 16 | @Input() description: string; 17 | 18 | 19 | /** 20 | * Automatically add a sectionned to the layout. 21 | */ 22 | ngOnInit() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/library/layout/layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsLayout:not([sectioned])', 8 | template: ``, 9 | host: { 10 | '[class.Polaris-Layout]': 'true' 11 | } 12 | }) 13 | export class LayoutComponent implements OnInit { 14 | 15 | /** 16 | * Automatically add a sectionned to the layout. 17 | */ 18 | ngOnInit() { 19 | } 20 | } 21 | 22 | /** 23 | * Component to display a Shopify layout 24 | */ 25 | @Component({ 26 | selector: 'plrsLayout[sectioned]', 27 | template: ``, 28 | host: { 29 | '[class.Polaris-Layout]': 'true' 30 | } 31 | }) 32 | export class SectionedLayoutComponent implements OnInit { 33 | 34 | /** 35 | * Automatically add a sectionned to the layout. 36 | */ 37 | ngOnInit() { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/library/layout/section.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsSection', 8 | template: ``, 9 | host: { 10 | '[class.Polaris-Layout__Section]': 'true', 11 | '[class.Polaris-Layout__Section--secondary]': 'secondary !== false' 12 | }, 13 | styles: [':host {display: block;}'] 14 | }) 15 | export class SectionComponent implements OnInit { 16 | 17 | /** 18 | * Whatever this section is secondary 19 | */ 20 | @Input('secondary') secondary: boolean = false; 21 | 22 | ngOnInit() { } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/library/page/page.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/library/page/page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { PaginationDescriptor } from '../pagination/pagination.descriptor'; 3 | import { AngularComplexAction } from '../types'; 4 | 5 | /** 6 | * Component to display a Shopify layout 7 | */ 8 | @Component({ 9 | selector: 'plrsPage', 10 | templateUrl: './page.component.html', 11 | host: { 12 | '[class.Polaris-Page]': 'true', 13 | '[class.Polaris-Page--fullWidth]': 'fullWidth !== false' 14 | }, 15 | styles: [':host {display: block;}'] 16 | }) 17 | export class PageComponent implements OnInit { 18 | 19 | 20 | @Input() public title: string = ''; 21 | @Input() public icon: string = ''; 22 | @Input() public breadcrumbs: AngularComplexAction[]; 23 | @Input() public secondaryActions: AngularComplexAction[]; 24 | @Input() public primaryAction: AngularComplexAction; 25 | @Input() public pagination: PaginationDescriptor; 26 | @Input() public fullWidth: boolean = false; 27 | 28 | /** 29 | * Automatically add a sectionned to the layout. 30 | */ 31 | ngOnInit() { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/library/page/page.header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 |
12 | 13 |

{{ title }}

14 | 15 |
16 | 17 | 18 |
19 | 20 | 25 | 26 | 27 | 28 | 29 | {{ action.content }} 30 | 31 | {{ action.content }} 32 | 33 | 38 | 39 | 40 | 41 | 42 | {{ action.content }} 43 | 44 | {{ action.content }} 45 | 46 | 59 | 60 |
61 |
62 | -------------------------------------------------------------------------------- /src/app/library/page/page.header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { PaginationDescriptor } from '../pagination/pagination.descriptor'; 3 | import { AngularComplexAction } from '../types'; 4 | 5 | /** 6 | * Component to display a Shopify layout 7 | */ 8 | @Component({ 9 | selector: 'plrsPageHeader', 10 | templateUrl: './page.header.component.html', 11 | host: { 12 | '[class.Polaris-Page__Header]': 'true', 13 | '[class.Polaris-Page__Header--hasBreadcrumbs]': 'breadcrumbs !== undefined', 14 | '[class.Polaris-Page__Header--hasPagination]': 'pagination !== undefined', 15 | }, 16 | styles: [':host {display: block;}'], 17 | }) 18 | export class PageHeaderComponent implements OnInit { 19 | 20 | 21 | @Input() public title: string = ''; 22 | @Input() public icon: string = ''; 23 | @Input() public breadcrumbs: AngularComplexAction[]; 24 | @Input() public secondaryActions: AngularComplexAction[]; 25 | @Input() public primaryAction: AngularComplexAction; 26 | @Input() public pagination: PaginationDescriptor; 27 | 28 | /** 29 | * Automatically add a sectionned to the layout. 30 | */ 31 | ngOnInit() { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/library/pagination/index.ts: -------------------------------------------------------------------------------- 1 | export { PaginationComponent } from './pagination.component'; 2 | export { PaginationDescriptor } from './pagination.descriptor'; 3 | -------------------------------------------------------------------------------- /src/app/library/pagination/pagination.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 13 | 14 | 15 | 21 | 25 | 26 | 27 | 31 | 32 | 33 | 39 | -------------------------------------------------------------------------------- /src/app/library/pagination/pagination.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsPagination', 9 | templateUrl: './pagination.component.html', 10 | host: { 11 | '[class.Polaris-Pagination]': 'true', 12 | '[class.Polaris-Pagination--plain]': 'plain !== false' 13 | } 14 | }) 15 | export class PaginationComponent { 16 | 17 | @Input() public plain: boolean = false; 18 | 19 | @Input() public nextUrl: string; 20 | @Input() public previousUrl: string; 21 | 22 | @Input() public hasNext: boolean = false; 23 | @Input() public hasPrevious: boolean = false; 24 | 25 | @Output() public next: EventEmitter = new EventEmitter(); 26 | @Output() public previous: EventEmitter = new EventEmitter(); 27 | 28 | @Input() public nextRouterLink:string; 29 | @Input() public previousRouterLink:string; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/library/pagination/pagination.descriptor.ts: -------------------------------------------------------------------------------- 1 | export interface PaginationDescriptor { 2 | hasNext?: boolean, 3 | hasPrevious?: boolean, 4 | nextURL?: string, 5 | previousURL?: string, 6 | onNext?(): void, 7 | onPrevious?(): void, 8 | previousRouterLink?: string, 9 | nextRouterLink?: string, 10 | } 11 | -------------------------------------------------------------------------------- /src/app/library/radio.button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './radio.button.component' 2 | -------------------------------------------------------------------------------- /src/app/library/radio.button/radio.button.component.html: -------------------------------------------------------------------------------- 1 | 7 |
8 | 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/library/radio.button/radio.button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, forwardRef, Optional, Inject, TemplateRef, ViewChild } from '@angular/core'; 2 | import { NgModel, DefaultValueAccessor, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; 3 | import { AngularComplexAction } from '../types'; 4 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 5 | import { ValueAccessorBase } from '../form/value.accessor'; 6 | 7 | 8 | const getUniqueID = createUniqueIDFactory('RadioButton'); 9 | 10 | /** 11 | * Component to display a Shopify layout 12 | */ 13 | @Component({ 14 | selector: 'plrsRadioButton', 15 | templateUrl: 'radio.button.component.html', 16 | providers: [ 17 | {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RadioButtonComponent), multi: true} 18 | ], 19 | host: { 20 | } 21 | }) 22 | export class RadioButtonComponent extends ValueAccessorBase { 23 | 24 | /** 25 | * Additional hint text to display. 26 | */ 27 | @Input() helpText: string; 28 | @Input() label: string; 29 | @Input() labelHidden: boolean; 30 | @Input() disabled: boolean = false; 31 | @Input() error: Error; 32 | @Input() name: string; 33 | 34 | private _id = getUniqueID(); 35 | 36 | @Input() 37 | get id(): string {return this._id;} 38 | set id(value:string) { this._id = value ? value : this._id;} 39 | 40 | @Input('value') radioVal: string; 41 | 42 | @Output() focus: EventEmitter = new EventEmitter(); 43 | @Output() blur: EventEmitter = new EventEmitter(); 44 | 45 | get checked(): boolean { 46 | return this.value == this.radioVal; 47 | } 48 | 49 | triggerUpdate(event: Event) { 50 | if (event.srcElement['checked']) { 51 | this.value = this.radioVal; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/app/library/resource.list/resource.list.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/library/resource.list/resource.list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsResourceList', 8 | templateUrl: './resource.list.component.html', 9 | host: { 10 | '[class.Polaris-ResourceList]': 'true', 11 | } 12 | }) 13 | export class ResourceListComponent implements OnInit { 14 | 15 | ngOnInit() { } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/library/resource.list/resource.list.item.component.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/app/library/resource.list/resource.list.item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, HostListener, TemplateRef } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsResourceListItem', 8 | templateUrl: './resource.list.item.component.html', 9 | host: { 10 | '[class.Polaris-ResourceList__ItemWrapper]': 'true', 11 | }, 12 | styles: [':host {display: list-item;}'] 13 | }) 14 | export class ResourceListItemComponent implements OnInit { 15 | 16 | @Input() url: string; 17 | @Input() attributeOne: string; 18 | @Input() attributeTwo: string; 19 | @Input() attributeThree: string; 20 | @Input() routerLink: string = ''; 21 | @Input() media: string|TemplateRef = ""; 22 | @Input() badges: {status: string, content: string}[] = []; 23 | 24 | 25 | state = { 26 | actionsMenuVisible: false, 27 | focused: false, 28 | }; 29 | 30 | ngOnInit() { } 31 | 32 | @HostListener('mouseenter') @HostListener('focus') focus() { 33 | this.state.focused = true; 34 | } 35 | 36 | @HostListener('mouseleave') @HostListener('blur') blur() { 37 | this.state.focused = false; 38 | } 39 | 40 | 41 | ngClass() { 42 | return { 43 | 'Polaris-ResourceList__Item--focused': this.state.focused, 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/app/library/select/select.component.html: -------------------------------------------------------------------------------- 1 | 2 |
5 | 21 |
22 |
23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /src/app/library/select/select.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, forwardRef, Optional, Inject, TemplateRef, ViewChild } from '@angular/core'; 2 | import { NgModel, DefaultValueAccessor, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; 3 | import { AngularComplexAction } from '../types'; 4 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 5 | import { ElementBase} from '../form/element.base'; 6 | 7 | 8 | const getUniqueID = createUniqueIDFactory('Select'); 9 | 10 | export type Option = string | { 11 | value: string, 12 | label: string, 13 | }; 14 | 15 | /** 16 | * Component to display a Shopify layout 17 | */ 18 | @Component({ 19 | selector: 'plrsSelect', 20 | templateUrl: 'select.component.html', 21 | providers: [ 22 | {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SelectComponent), multi: true} 23 | ], 24 | }) 25 | export class SelectComponent extends ElementBase implements OnInit { 26 | 27 | ngOnInit() { } 28 | 29 | constructor( 30 | @Optional() @Inject(NG_VALIDATORS) validators: Array, 31 | @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, 32 | ) { 33 | super(validators, asyncValidators); 34 | } 35 | 36 | @Input() options: Option[] = []; 37 | 38 | /** 39 | * Hint text to display 40 | */ 41 | @Input() placeholder: string = ""; 42 | 43 | // groups?: (Group | Option)[], 44 | /** 45 | * Additional hint text to display. 46 | */ 47 | @Input() helpText: string; 48 | @Input() label: string; 49 | @Input() labelAction: AngularComplexAction; 50 | @Input() labelHidden: boolean; 51 | @Input() disabled: boolean = false; 52 | @Input() autoFocus: boolean; 53 | @Input() multiline: boolean | number; 54 | @Input() error: Error; 55 | @Input() name: string; 56 | @Input() id = getUniqueID(); 57 | @Input() step: number; 58 | @Input() autoComplete: boolean; 59 | @Input() max: number; 60 | @Input() maxLength: number; 61 | @Input() min: number; 62 | @Input() minLength: number; 63 | @Input() pattern: string; 64 | @Input() spellCheck: boolean; 65 | @Input() onChange: (value: string) => void; 66 | @Input() onFocus: () => void; 67 | @Input() onBlur: () => void; 68 | @Input() required: boolean = false; 69 | @Input() value: string; 70 | 71 | 72 | @ViewChild(NgModel) model: NgModel; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/app/library/stack/stack.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | // import { Alignment, Spacing, Distribution} from '@shopify/polaris'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsStack', 9 | template: ``, 10 | host: { 11 | '[class.Polaris-Stack]': 'true', 12 | '[class.Polaris-Stack--vertical]': 'vertical !== false', 13 | 14 | '[class.Polaris-Stack--spacingTight]': 'spacing == "tight"', 15 | '[class.Polaris-Stack--spacingLoose]': 'spacing == "loose"', 16 | 17 | '[class.Polaris-Stack--alignmentLeading]': 'alignment == "leading"', 18 | '[class.Polaris-Stack--alignmentTrailing]': 'alignment == "trailing"', 19 | '[class.Polaris-Stack--alignmentCenter]': 'alignment == "center"', 20 | '[class.Polaris-Stack--alignmentFill]': 'alignment == "fill"', 21 | '[class.Polaris-Stack--alignmentBaseline]': 'alignment == "baseline"', 22 | 23 | 24 | '[class.Polaris-Stack--distributionEqualSpacing]': 'distribution == "equalSpacing"', 25 | '[class.Polaris-Stack--distributionLeading]': 'distribution == "leading"', 26 | '[class.Polaris-Stack--distributionTrailing]': 'distribution == "trailing"', 27 | '[class.Polaris-Stack--distributionCenter]': 'distribution == "center"', 28 | '[class.Polaris-Stack--distributionFill]': 'distribution == "fill"', 29 | '[class.Polaris-Stack--distributionFillEvenly]': 'distribution == "fillEvenly"', 30 | 31 | } 32 | }) 33 | export class StackComponent implements OnInit { 34 | 35 | /** 36 | * Automatically add a sectionned to the layout. 37 | */ 38 | ngOnInit() { 39 | } 40 | 41 | /** 42 | * Stack the elements vertically 43 | */ 44 | @Input() vertical: boolean = false; 45 | 46 | /** 47 | * Adjust spacing between elements 48 | */ 49 | @Input() spacing: string = 'none'; 50 | 51 | /** 52 | * Adjust alignment of elements 53 | */ 54 | @Input() alignment: string; 55 | 56 | /** 57 | * Distribution alignment of elements 58 | */ 59 | @Input() distribution: string; 60 | } 61 | -------------------------------------------------------------------------------- /src/app/library/stack/stack.item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsStackItem', 8 | template: ``, 9 | host: { 10 | '[class.Polaris-Stack__Item]': 'true', 11 | '[class.Polaris-Stack__Item--fill]': 'fill !== false' 12 | } 13 | }) 14 | export class StackItemComponent implements OnInit { 15 | 16 | /** 17 | * Automatically add a sectionned to the layout. 18 | */ 19 | ngOnInit() { 20 | } 21 | 22 | /** 23 | * Fill the stack with this item. 24 | */ 25 | @Input() fill: boolean = false; 26 | } 27 | -------------------------------------------------------------------------------- /src/app/library/text.field/resizer.component.html: -------------------------------------------------------------------------------- 1 |
{{ contents }}
2 |
{{ minimumLineContent }}
3 | -------------------------------------------------------------------------------- /src/app/library/text.field/resizer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, OnChanges, EventEmitter, ViewChild, ElementRef, HostListener } from '@angular/core'; 2 | import { NgModel } from '@angular/forms'; 3 | 4 | 5 | /** 6 | * Component to display a Shopify layout 7 | */ 8 | @Component({ 9 | selector: 'plrsResizer', 10 | templateUrl: 'resizer.component.html', 11 | host: { 12 | '[class.Polaris-TextField__Resizer]': 'true', 13 | '[attr.aria-hidden]': '"true"' 14 | } 15 | }) 16 | export class ResizerComponent implements OnInit, OnChanges { 17 | 18 | @ViewChild('contentNode') private contentNode: ElementRef; 19 | @ViewChild('minimumLinesNode') private minimumLinesNode: ElementRef; 20 | 21 | @Output() heightChange: EventEmitter = new EventEmitter(); 22 | 23 | @Input() contents: string = ''; 24 | 25 | /** 26 | * @todo Handle properly 27 | */ 28 | private currentHeight: number = 0; 29 | 30 | /** 31 | * The minimal number of lines the content should have. 32 | */ 33 | @Input() minimumLines: number = 1; 34 | 35 | /** 36 | * Dummy content for building the minimum line object 37 | */ 38 | public get minimumLineContent(): string { 39 | return " \n".repeat(Math.max(1, this.minimumLines)); 40 | } 41 | 42 | /** 43 | * Retrieve the height of the nodes. 44 | */ 45 | private getHeight(): number { 46 | const contentHeight = this.contentNode.nativeElement.offsetHeight; 47 | const minHeight = this.minimumLinesNode.nativeElement.offsetHeight; 48 | return Math.max(contentHeight, minHeight); 49 | } 50 | 51 | @HostListener('window:resize', ['$event']) 52 | ngOnChanges() { 53 | setTimeout(() => { 54 | const updatedHeight = this.getHeight(); 55 | if (updatedHeight != this.currentHeight) { 56 | this.currentHeight = updatedHeight; 57 | this.heightChange.emit(updatedHeight); 58 | } 59 | }, 0); 60 | } 61 | 62 | ngOnInit() { 63 | this.ngOnChanges(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/app/library/text.field/spinner.component.html: -------------------------------------------------------------------------------- 1 |
6 |
7 | 8 |
9 |
10 |
15 |
16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/app/library/text.field/spinner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, HostListener } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsSpinner', 8 | templateUrl: 'spinner.component.html', 9 | host: { 10 | '[class.Polaris-TextField__Spinner]': 'true', 11 | '[attr.aria-hidden]': '"true"' 12 | } 13 | }) 14 | export class SpinnerComponent { 15 | 16 | ngOnInit() { } 17 | 18 | constructor() { } 19 | 20 | @Output() change: EventEmitter = new EventEmitter(); 21 | @Input() onChange: {(delta: number): void} = (delta: number) => {}; 22 | 23 | @Input() onClick: {(): void} = () => {}; 24 | 25 | public handleStep(step: number) { 26 | this.onChange(step); 27 | this.change.emit(step); 28 | } 29 | 30 | @HostListener('click') public handleClick() { 31 | this.onClick(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/app/library/text.field/text.field.component.html: -------------------------------------------------------------------------------- 1 | 2 |
6 | 7 |
8 | 9 |
10 |
11 | 31 | 32 | 48 | 52 | 53 | 54 |
55 |
56 | 57 |
58 | -------------------------------------------------------------------------------- /src/app/library/text.field/text.field.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter, forwardRef, Optional, Inject, TemplateRef, ViewChild, ElementRef } from '@angular/core'; 2 | import { NgModel, DefaultValueAccessor, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; 3 | import { AngularComplexAction } from '../types'; 4 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 5 | import { ElementBase} from '../form/element.base'; 6 | 7 | 8 | const getUniqueID = createUniqueIDFactory('TextField'); 9 | 10 | /** 11 | * Component to display a Shopify layout 12 | */ 13 | @Component({ 14 | selector: 'plrsTextField', 15 | templateUrl: 'text.field.component.html', 16 | providers: [ 17 | {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TextFieldComponent), multi: true} 18 | ], 19 | }) 20 | export class TextFieldComponent extends ElementBase implements OnInit { 21 | 22 | ngOnInit() { } 23 | 24 | @Output() change: EventEmitter = new EventEmitter(); 25 | 26 | constructor( 27 | @Optional() @Inject(NG_VALIDATORS) validators: Array, 28 | @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, 29 | ) { 30 | super(validators, asyncValidators); 31 | } 32 | 33 | @Input() prefix: string|TemplateRef = ""; 34 | @Input() suffix: string; 35 | 36 | /** 37 | * Hint text to display 38 | */ 39 | @Input() placeholder: string|TemplateRef = ""; 40 | 41 | /** 42 | * Initial value for the input 43 | */ 44 | @Input() value: string; 45 | 46 | /** 47 | * Additional hint text to display. 48 | */ 49 | @Input() helpText: string; 50 | @Input() label: string; 51 | @Input() labelAction: AngularComplexAction; 52 | @Input() labelHidden: boolean; 53 | @Input() disabled: boolean = false; 54 | @Input() readOnly: boolean = false; 55 | @Input() autoFocus: boolean; 56 | @Input() multiline: boolean | number = false; 57 | @Input() error: Error; 58 | // @Input() connectedRight?: React.ReactNode; 59 | // @Input() connectedLeft?: React.ReactNode; 60 | @Input() type: string; 61 | @Input() name: string; 62 | @Input() id = getUniqueID(); 63 | @Input() step: number = 1; 64 | @Input() autoComplete: boolean; 65 | @Input() max: number = Infinity; 66 | @Input() maxLength: number; 67 | @Input() min: number = -Infinity; 68 | @Input() minLength: number; 69 | @Input() pattern: string; 70 | @Input() spellCheck: boolean; 71 | @Input() onChange: (value: string) => void; 72 | @Input() onFocus: () => void; 73 | @Input() onBlur: () => void; 74 | @Input() required: boolean = false; 75 | 76 | 77 | @ViewChild('fieldInput') private fieldInput: ElementRef; 78 | @ViewChild('fieldTextarea') private fieldTextarea: ElementRef; 79 | private get field(): ElementRef { 80 | return (this.multiline === false) ? this.fieldInput : this.fieldTextarea; 81 | } 82 | 83 | @ViewChild('textareaModel') private textareaModel: NgModel; 84 | @ViewChild('inputModel') private inputModel: NgModel; 85 | get model(): NgModel { 86 | return (this.multiline === false) ? this.inputModel : this.textareaModel; 87 | } 88 | 89 | 90 | @Output() keyup = new EventEmitter(); 91 | 92 | private triggerKeyUp(event: KeyboardEvent) { 93 | this.keyup.emit(event); 94 | } 95 | 96 | private triggerChange(value: string) { 97 | this.change.emit(value); 98 | if (typeof this.onChange == 'function'){ 99 | this.onChange(this.value); 100 | } 101 | } 102 | 103 | private handleInputFocus() { 104 | this.field.nativeElement.focus(); 105 | } 106 | 107 | private handleNumberChange(steps: number) { 108 | const numericValue = this.value ? parseFloat(this.value) : 0; 109 | if (isNaN(numericValue)) { return; } 110 | const newValue = Math.min(this.max, Math.max(numericValue + (steps * this.step), this.min)); 111 | 112 | if (newValue != numericValue) { 113 | this.value = newValue.toString(); 114 | this.triggerChange(this.value); 115 | } 116 | 117 | } 118 | 119 | private displayLimit(value: number): string { 120 | if (this.type == 'number' && isFinite(value)) { 121 | return value.toString(); 122 | } else { 123 | return ''; 124 | } 125 | } 126 | 127 | public resizeTextarea(height: number) { 128 | if (this.fieldTextarea !== undefined) { 129 | this.fieldTextarea.nativeElement.style.height = `${height}px`; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/app/library/thumbnail/thumbnail.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/library/thumbnail/thumbnail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsThumbnail', 8 | templateUrl: './thumbnail.component.html', 9 | host: { 10 | '[class.Polaris-Thumbnail]': 'true', 11 | '[class.Polaris-Thumbnail--sizeSmall]': 'size == "small"', 12 | '[class.Polaris-Thumbnail--sizeMedium]': 'size == "medium"', 13 | '[class.Polaris-Thumbnail--sizeLarge]': 'size == "large"', 14 | } 15 | }) 16 | export class ThumbnailComponent implements OnInit { 17 | 18 | ngOnInit() { } 19 | 20 | /** 21 | * Size of thumbnail 22 | */ 23 | @Input() size: 'small' | 'medium' | 'large' = 'medium'; 24 | 25 | /** 26 | * URL for the avatar image 27 | */ 28 | @Input() source: string; 29 | 30 | /** 31 | * Alt text for the thumbnail image 32 | */ 33 | @Input() alt: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/app/library/types.ts: -------------------------------------------------------------------------------- 1 | export interface Action { 2 | content?: string, 3 | accessibilityLabel?: string, 4 | url?: string, 5 | onAction?(): void, 6 | } 7 | 8 | export interface LinkAction { 9 | content?: string, 10 | accessibilityLabel?: string, 11 | url: string, 12 | } 13 | 14 | export interface CallbackAction { 15 | content?: string, 16 | accessibilityLabel?: string, 17 | onAction(): void, 18 | } 19 | 20 | export interface DisableableAction extends Action { 21 | disabled?: boolean, 22 | } 23 | 24 | export interface DestructableAction extends Action { 25 | destructive?: boolean, 26 | } 27 | 28 | export interface IconableAction extends Action { 29 | icon?: any, 30 | } 31 | 32 | export interface ComplexAction extends Action, DisableableAction, DestructableAction, IconableAction { 33 | } 34 | 35 | export interface AngularComplexAction extends ComplexAction { 36 | routerLink?: string; 37 | plain?: boolean; 38 | } 39 | 40 | /** 41 | * Used to relay options to the Select Componenent and the Choice List Componenent 42 | */ 43 | export type Option = string | { 44 | value: any, 45 | label: string, 46 | }; 47 | -------------------------------------------------------------------------------- /src/app/library/unstyled.link/unstyled.link.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | {{ children }} 10 | 11 | 20 | 21 | {{ children }} 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/app/library/unstyled.link/unstyled.link.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsUnstyledLink', 9 | host: { 10 | '[attr.data-polaris-unstyled]': 'true', 11 | }, 12 | templateUrl: './unstyled.link.component.html', 13 | styles: [':host {display: inline;}'], 14 | }) 15 | export class UnstyledLinkComponent { 16 | 17 | @Input() set fromAction(action:AngularComplexAction) { 18 | this.children = action.content; 19 | this.accessibilityLabel = action.accessibilityLabel; 20 | 21 | // Wire links if need be 22 | if (action.routerLink) { 23 | this.routerLink = action.routerLink; 24 | } else if (action.url) { 25 | this.url = action.url; 26 | } 27 | 28 | // Handle subsciprion to the click event 29 | this.action = new EventEmitter(); 30 | if (action.onAction) { 31 | this.action.subscribe(action.onAction); 32 | } 33 | 34 | this.plain = action.plain ? true : false; 35 | } 36 | 37 | /** 38 | * The content to display inside the button. 39 | */ 40 | @Input() children:string; 41 | 42 | 43 | @Input() accessibilityLabel:string; 44 | 45 | /** 46 | * URL to link to 47 | */ 48 | @Input() url:string; 49 | 50 | @Output() action: EventEmitter = new EventEmitter(); 51 | 52 | public onClick(event: MouseEvent) { 53 | if (this.action.observers.length > 0) { 54 | event.preventDefault(); 55 | this.action.emit(); 56 | } 57 | } 58 | 59 | /** 60 | * Display as primary button 61 | */ 62 | @Input() primary: boolean = false; 63 | 64 | /** 65 | * Display as destructive button 66 | */ 67 | @Input() destructive: boolean = false; 68 | 69 | /** 70 | * Display as disabled button. 71 | */ 72 | @Input() disabled: boolean = false; 73 | 74 | /** 75 | * Display as disabled button. 76 | */ 77 | @Input() plain: boolean = false; 78 | 79 | /** 80 | * Hack as an Angular router link. 81 | */ 82 | @Input() routerLink: string; 83 | 84 | @Input() inGroup: boolean = false; 85 | 86 | @Input() outline: boolean = false; 87 | 88 | @Input() external: boolean = false; 89 | 90 | @Input() iconOnly: boolean = false; 91 | 92 | @Input() icon: string; 93 | 94 | @Input() classNames: string; 95 | 96 | 97 | /** 98 | * Apply classes to the inner button. 99 | */ 100 | @Input() innerClass: string = ''; 101 | 102 | ngClass() { 103 | return { 104 | 'Polaris-Button--primary': this.primary !== false, 105 | 'Polaris-Button--destructive': this.destructive !== false, 106 | 'Polaris-Button--disabled': this.disabled !== false, 107 | 'Polaris-Button--plain': this.plain !== false, 108 | 'Polaris-Button--outline': this.outline !== false, 109 | 'Polaris-Button--iconOnly': this.iconOnly !== false, 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/app/library/utilities/template.or.string.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, TemplateRef } from '@angular/core'; 2 | import { AngularComplexAction } from '../types'; 3 | 4 | /** 5 | * Component to display a Shopify layout 6 | */ 7 | @Component({ 8 | selector: 'plrsTempalteOrString', 9 | template: `{{ value }}`, 10 | styles: [ 11 | ':host {display: block;}', 12 | ':host(.displayInline) {display: inline;}', 13 | ':host(.displayInlineBlock) {display: inline-block;}', 14 | ':host(.displayFlex) {display: flex;}', 15 | ':host(.displayNone) {display: none;}', 16 | ], 17 | host: { 18 | '[class.displayInline]': 'display == "inline"', 19 | '[class.displayInlineBlock]': 'display == "inline-block"', 20 | '[class.displayFlex]': 'display == "flex"', 21 | '[class.displayNone]': 'display == "none"', 22 | } 23 | }) 24 | export class TemplateOrStringComponent implements OnInit { 25 | 26 | @Input() value: string|TemplateRef = ''; 27 | 28 | private _display: string = 'block'; 29 | 30 | @Input() get display(): string { 31 | if (this.value) { 32 | return this._display; 33 | } else { 34 | return 'none'; 35 | } 36 | } 37 | set display(value: string) { 38 | this._display = value; 39 | } 40 | 41 | ngOnInit() { } 42 | 43 | renderAsString(): boolean { 44 | return ! (this.value && typeof this.value == 'object') 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/app/library/validators/hexadecimal-validator.ts: -------------------------------------------------------------------------------- 1 | // import {Directive} from '@angular/core'; 2 | // 3 | // import { 4 | // NG_VALIDATORS, 5 | // AbstractControl, 6 | // } from '@angular/forms'; 7 | // 8 | // @Directive({ 9 | // selector: '[hexadecimal][ngModel]', 10 | // providers: [ 11 | // { provide: NG_VALIDATORS, useExisting: HexadecimalValueValidator, multi: true } 12 | // ] 13 | // }) 14 | // export class HexadecimalValueValidator { 15 | // validate(control: AbstractControl): {[validator: string]: string} { 16 | // const expression = /^([0-9a-fA-F]+)$/i; 17 | // if (!control.value) { // the [required] validator will check presence, not us 18 | // return null; 19 | // } 20 | // 21 | // const value = control.value.trim(); 22 | // if (expression.test(value)) { 23 | // return null; 24 | // } 25 | // 26 | // return {hexadecimal: 'Please enter a hexadecimal value (alphanumeric, 0-9 and A-F)'}; 27 | // } 28 | // } 29 | -------------------------------------------------------------------------------- /src/app/library/visually.hidden/visually.hidden.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, TemplateRef } from '@angular/core'; 2 | 3 | /** 4 | * Component to display a Shopify layout 5 | */ 6 | @Component({ 7 | selector: 'plrsVisuallyHidden', 8 | template: '', 9 | host: { 10 | '[class.Polaris-VisuallyHidden]': 'true', 11 | }, 12 | }) 13 | export class VisuallyHiddenComponent implements OnInit { 14 | 15 | ngOnInit() { } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/library/wysiwyg/wysiwyg.component.css: -------------------------------------------------------------------------------- 1 | :host /deep/ .ql-toolbar.ql-snow, 2 | :host /deep/ .ql-toolbar.ql-snow + .ql-container.ql-snow { 3 | border: 1px solid #c4cdd5; 4 | } 5 | 6 | :host /deep/ .ql-toolbar.ql-snow { 7 | border-top-left-radius: 3px; 8 | border-top-right-radius: 3px; 9 | border-bottom: none; 10 | background: #fafbfc; 11 | line-height: 2.5; 12 | } 13 | 14 | :host /deep/ .ql-toolbar.ql-snow + .ql-container.ql-snow { 15 | border-bottom-left-radius: 3px; 16 | border-bottom-right-radius: 3px; 17 | border-top: 1px solid #c4cdd5; 18 | -webkit-backface-visibility: hidden; 19 | backface-visibility: hidden; 20 | will-change: box-shadow; 21 | -webkit-transition: box-shadow .2s cubic-bezier(.64,0,.35,1); 22 | transition: box-shadow .2s cubic-bezier(.64,0,.35,1); 23 | box-shadow: inset 0 1px 0 0 rgba(99,115,129,.05); 24 | } 25 | 26 | :host(.focus) { 27 | background: red !important; 28 | } 29 | 30 | :host(.focus) /deep/ .ql-toolbar.ql-snow + .ql-container.ql-snow { 31 | border-color: #5c6ac4; 32 | box-shadow: inset 0 0 0 0 transparent, 0 0 0 1px #5c6ac4; 33 | } 34 | 35 | :host /deep/ .ql-formats button, 36 | :host /deep/ .ql-formats .ql-picker { 37 | border: 1px solid #c4cdd5; 38 | border-left: none; 39 | line-height: 1; 40 | } 41 | 42 | :host /deep/ .ql-formats button:first-child, 43 | :host /deep/ .ql-formats .ql-picker:first-child { 44 | border-top-left-radius: 3px; 45 | border-bottom-left-radius: 3px; 46 | border-left: 1px solid #c4cdd5; 47 | } 48 | 49 | :host /deep/ .ql-formats button:last-child, 50 | :host /deep/ .ql-formats .ql-picker:nth-last-child(2) { 51 | border-top-right-radius: 3px; 52 | border-bottom-right-radius: 3px; 53 | } 54 | -------------------------------------------------------------------------------- /src/app/library/wysiwyg/wysiwyg.component.html: -------------------------------------------------------------------------------- 1 | 6 |
7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /src/app/library/wysiwyg/wysiwyg.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Code for this class was copied and adapted from the 3 | * (NGX-Quill component)[https://github.com/KillerCodeMonkey/ngx-quill/blob/develop/src/quill-editor.component.ts]. 4 | * 5 | * Original copyright go to Bengt Weiße (aka: KillerCodeMonkey). The original code was distributed under an MIT licence. 6 | * 7 | */ 8 | 9 | import { 10 | AfterViewInit, 11 | Component, 12 | ElementRef, 13 | EventEmitter, 14 | forwardRef, 15 | Input, 16 | OnChanges, 17 | Output, 18 | SimpleChanges, 19 | ViewEncapsulation 20 | } from '@angular/core'; 21 | 22 | import { 23 | NG_VALUE_ACCESSOR, 24 | NG_VALIDATORS, 25 | ControlValueAccessor, 26 | Validator 27 | } from '@angular/forms'; 28 | 29 | import * as Quill from 'quill'; 30 | import { AngularComplexAction } from '../types'; 31 | import { createUniqueIDFactory } from '@shopify/javascript-utilities/other'; 32 | 33 | 34 | const getUniqueID = createUniqueIDFactory('Wysiwyg'); 35 | 36 | /** 37 | * Componenent for rendering a WYSIWYG 38 | */ 39 | @Component({ 40 | selector: 'plrsWysiwyg', 41 | templateUrl: 'wysiwyg.component.html', 42 | styleUrls: ['./wysiwyg.component.css'], 43 | providers: [{ 44 | provide: NG_VALUE_ACCESSOR, 45 | useExisting: forwardRef(() => WysiwygComponent), 46 | multi: true 47 | }, { 48 | provide: NG_VALIDATORS, 49 | useExisting: forwardRef(() => WysiwygComponent), 50 | multi: true 51 | }], 52 | host: { 53 | '[class.focus]': 'focus', 54 | }, 55 | 56 | }) 57 | export class WysiwygComponent implements AfterViewInit, ControlValueAccessor, OnChanges, Validator { 58 | 59 | quillEditor: any; 60 | editorElem: HTMLElement; 61 | emptyArray: any[] = []; 62 | content: any; 63 | defaultModules = { 64 | toolbar: [ 65 | ['bold', 'italic', 'underline', 'strike'], // toggled buttons 66 | ['blockquote', 'code-block'], 67 | 68 | [{ 'header': 1 }, { 'header': 2 }], // custom button values 69 | [{ 'list': 'ordered' }, { 'list': 'bullet' }], 70 | [{ 'script': 'sub' }, { 'script': 'super' }], // superscript/subscript 71 | [{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent 72 | [{ 'direction': 'rtl' }], // text direction 73 | 74 | [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown 75 | [{ 'header': [1, 2, 3, 4, 5, 6, false] }], 76 | 77 | [{ 'color': this.emptyArray.slice() }, { 'background': this.emptyArray.slice() }], // dropdown with defaults from theme 78 | [{ 'font': this.emptyArray.slice() }], 79 | [{ 'align': this.emptyArray.slice() }], 80 | 81 | ['clean'], // remove formatting button 82 | 83 | ['link', 'image', 'video'] // link and image, video 84 | ] 85 | }; 86 | 87 | @Input() id = getUniqueID(); 88 | @Input() helpText: string; 89 | @Input() theme: string; 90 | @Input() modules: { [index: string]: Object }; 91 | @Input() readOnly: boolean; 92 | @Input() placeholder: string; 93 | @Input() maxLength: number; 94 | @Input() minLength: number; 95 | @Input() required: boolean; 96 | @Input() formats: string[]; 97 | @Input() bounds: HTMLElement | string; 98 | @Input() label: string; 99 | @Input() labelAction: AngularComplexAction; 100 | @Input() labelHidden: boolean; 101 | @Input() 102 | get toolbarOpts(): any[] { 103 | return this.defaultModules.toolbar; 104 | } 105 | set toolbarOpts(value: any[]) { 106 | this.defaultModules.toolbar = value; 107 | } 108 | 109 | @Output() onEditorCreated: EventEmitter = new EventEmitter(); 110 | @Output() onContentChanged: EventEmitter = new EventEmitter(); 111 | @Output() onSelectionChanged: EventEmitter = new EventEmitter(); 112 | 113 | onModelChange: Function = () => { }; 114 | onModelTouched: Function = () => { }; 115 | 116 | constructor(private elementRef: ElementRef) { } 117 | 118 | ngAfterViewInit() { 119 | const toolbarElem = this.elementRef.nativeElement.querySelector('[quill-editor-toolbar]'); 120 | let modules: any = this.modules || this.defaultModules; 121 | let placeholder = 'Insert text here ...'; 122 | 123 | if (this.placeholder !== null && this.placeholder !== undefined) { 124 | placeholder = this.placeholder.trim(); 125 | } 126 | 127 | if (toolbarElem) { 128 | modules['toolbar'] = toolbarElem; 129 | } 130 | this.elementRef.nativeElement.insertAdjacentHTML('beforeend', '
'); 131 | this.editorElem = this.elementRef.nativeElement.querySelector('[quill-editor-element]'); 132 | 133 | this.quillEditor = new Quill(this.editorElem, { 134 | modules: modules, 135 | placeholder: placeholder, 136 | readOnly: this.readOnly || false, 137 | theme: this.theme || 'snow', 138 | formats: this.formats 139 | }); 140 | 141 | if (this.content) { 142 | const contents = this.quillEditor.clipboard.convert(this.content); 143 | this.quillEditor.setContents(contents); 144 | this.quillEditor.history.clear(); 145 | } 146 | 147 | this.onEditorCreated.emit(this.quillEditor); 148 | 149 | // mark model as touched if editor lost focus 150 | this.quillEditor.on('selection-change', (range: any, oldRange: any, source: string) => { 151 | this.onSelectionChanged.emit({ 152 | editor: this.quillEditor, 153 | range: range, 154 | oldRange: oldRange, 155 | source: source, 156 | bounds: this.bounds || document.body 157 | }); 158 | 159 | if (!range) { 160 | this.onModelTouched(); 161 | } 162 | }); 163 | 164 | // update model if text changes 165 | this.quillEditor.on('text-change', (delta: any, oldDelta: any, source: string) => { 166 | let html: (string | null) = this.editorElem.children[0].innerHTML; 167 | const text = this.quillEditor.getText(); 168 | 169 | if (html === '


') { 170 | html = null; 171 | } 172 | 173 | this.onModelChange(html); 174 | 175 | this.onContentChanged.emit({ 176 | editor: this.quillEditor, 177 | html: html, 178 | text: text, 179 | delta: delta, 180 | oldDelta: oldDelta, 181 | source: source 182 | }); 183 | }); 184 | } 185 | 186 | ngOnChanges(changes: SimpleChanges) { 187 | if (changes['readOnly'] && this.quillEditor) { 188 | this.quillEditor.enable(!changes['readOnly'].currentValue); 189 | } 190 | } 191 | 192 | writeValue(currentValue: any) { 193 | this.content = currentValue; 194 | 195 | if (this.quillEditor) { 196 | if (currentValue) { 197 | this.quillEditor.pasteHTML(currentValue); 198 | return; 199 | } 200 | this.quillEditor.setText(''); 201 | } 202 | } 203 | 204 | registerOnChange(fn: Function): void { 205 | this.onModelChange = fn; 206 | } 207 | 208 | registerOnTouched(fn: Function): void { 209 | this.onModelTouched = fn; 210 | } 211 | 212 | validate() { 213 | if (!this.quillEditor) { 214 | return null; 215 | } 216 | 217 | let err: { 218 | minLengthError?: { given: number, minLength: number }; 219 | maxLengthError?: { given: number, maxLength: number }; 220 | requiredError?: { empty: boolean } 221 | } = {}, 222 | valid = true; 223 | 224 | const textLength = this.quillEditor.getText().trim().length; 225 | 226 | if (this.minLength && textLength && textLength < this.minLength) { 227 | err.minLengthError = { 228 | given: textLength, 229 | minLength: this.minLength 230 | }; 231 | 232 | valid = false; 233 | } 234 | 235 | if (this.maxLength && textLength > this.maxLength) { 236 | err.maxLengthError = { 237 | given: textLength, 238 | maxLength: this.maxLength 239 | }; 240 | 241 | valid = false; 242 | } 243 | 244 | if (this.required && !textLength) { 245 | err.requiredError = { 246 | empty: true 247 | }; 248 | 249 | valid = false; 250 | } 251 | 252 | return valid ? null : err; 253 | } 254 | 255 | public get focus(): boolean { 256 | return this.quillEditor !== undefined && 257 | typeof this.quillEditor.hasFocus === 'function' && 258 | this.quillEditor.hasFocus(); 259 | } 260 | 261 | public focusEditor() { 262 | if (this.quillEditor !== undefined) { 263 | this.quillEditor.focus(); 264 | } 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/app/not.found.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

The page you are looking for could not be found.

5 |

Raise an issue on Git Hub if you think this is a mistake.

6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /src/app/not.found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'not.found.component.html', 5 | }) 6 | export class NotFoundComponent { 7 | breadcrumbs = [ 8 | { 9 | content: "Home", 10 | routerLink: "/" 11 | } 12 | ]; 13 | primaryAction = { 14 | content: "Raise issue", 15 | url: "https://github.com/syrp-nz/angular-polaris/issues/new" 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | repo: 'https://github.com/syrp-nz/angular-polaris/' 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | repo: 'https://github.com/syrp-nz/angular-polaris/' 9 | }; 10 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- 1 |  h& ��(  @������������������71�U61��0-��2.�;������������������������������71�971��71��71��0-��0-��1-��3/�#������������������71�!71��71��71��71��71��0-��0-��0-��0-��1-��3/����������71�U71��OI��VQ��82��71��71��0-��0-��0-��UR��KH��0-��2.�=������71��71����������mh��71��71��0-��0-��[X����������0-��1.�{������71��71��C=����������71��71��0-��0-����������85��0-��0-��������71��71��71����������������������������������0-��0-��1-��������71��71��71��QL��������������������������?<��0-��0-��0-��������71��71��71��71����������;5��:7����������0-��0-��0-��0-�����71�#71��71��71��71��d_������}y����������IG��0-��0-��0-��0-��4/�71�M71��71��71��71��71������������������0-��0-��0-��0-��0-��3/�A71�k71��71��71��71��71��zv����������VS��0-��0-��0-��0-��0-��2.�e71�}71��71��71��71��71��93����������0-��0-��0-��0-��0-��0-��1.�{71�A71��71��71��71��71��71������db��0-��0-��0-��0-��0-��1-��0-�E������71�)71��71��71��71��=7��0-��0-��0-��0-��1-��3/�/���������������������71�A71��61��0-��0-��3.�9�����������������������������������������������( @ �������������������������������������������71��61��2.��3/�?������������������������������������������������������������������������������71�_71��71��71��0-��0-��2.��3/�������������������������������������������������������������������71�;71��71��71��71��71��0-��0-��0-��0-��3.ŭ4/� ������������������������������������������������������71�71��71��71��71��71��71��71��0-��0-��0-��0-��0-��1.��3/lj���������������������������������������������71� 71��71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��0-��1.��3/�_������������������������������������71��71��71��71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/�;������������������������71�Y71��71��71��71��71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/�������������������71��71��71��XS��wr��vr��vr��<7��71��71��71��71��71��0-��0-��0-��0-��0-��0-��wu��~|��~|��NK��0-��0-��2.��������������������71��71��71��UP��������������{w��71��71��71��71��71��0-��0-��0-��0-��0-��YW��������������DB��0-��0-��1.�����������������71�71��71��71��71������������������71��71��71��71��71��0-��0-��0-��0-��0-������������������0-��0-��0-��1.�����������������71�O71��71��71��71��hc��������������SN��71��71��71��71��0-��0-��0-��0-��B?��������������PM��0-��0-��0-��0-��50�������������71�}71��71��71��71��71������������������71��71��71��71��0-��0-��0-��0-������������������0-��0-��0-��0-��0-��3/�3������������71��71��71��71��71��71��~{����������������������������������������������������������][��0-��0-��0-��0-��0-��3/�i������������71��71��71��71��71��71��;5����������������������������������������������������������0-��0-��0-��0-��0-��0-��3/Ǘ������������71��71��71��71��71��71��71������������������������������������������������������mk��0-��0-��0-��0-��0-��0-��2.Ž������������71��71��71��71��71��71��71��D>������������������ie��ie��db��db������������������1.��0-��0-��0-��0-��0-��0-��2.��������������71��71��71��71��71��71��71��71������������������71��71��0-��0-��������������|z��0-��0-��0-��0-��0-��0-��0-��2.��������������71��71��71��71��71��71��71��71��RL��������������JD��71��0-��XU��������������52��0-��0-��0-��0-��0-��0-��0-��1.�����������71�+71��71��71��71��71��71��71��71��71������������������71��0-������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��50�������71�]71��71��71��71��71��71��71��71��71��ea��������������:4��QO��������������:7��0-��0-��0-��0-��0-��0-��0-��0-��0-��4/�5������71��71��71��71��71��71��71��71��71��71��71��������������wr������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��3/�k������71��71��71��71��71��71��71��71��71��71��71��zv��������������������������C@��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��3/Ǘ������71��71��71��71��71��71��71��71��71��71��71��:4��������������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.Ž������71��71��71��71��71��71��71��71��71��71��71��71����������������������MK��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��������71��71��71��71��71��71��71��71��71��71��71��71��B<������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��������71��71��71��71��71��71��71��71��71��71��71��71��71��������������[Y��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��1.��������71��71��71��71��71��71��71��71��71��71��71��71��71��OJ����������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��������71�71�y71��71��71��71��71��71��71��71��71��71��71��71������jg��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/Ɠ4/����������������71�71��71��71��71��71��71��71��71��71��71��PK��1.��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/Ǜ3/����������������������������������71�)71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��1.��3/ƥ4/�'���������������������������������������������������71�C71��71��71��71��71��0-��0-��0-��1.��3.ů3/�1���������������������������������������������������������������������71�a71��61��1.��2.ŷ3/�=��������������������������������������� -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Loading... 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-polaris", 3 | "version": "0.0.5", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/syrp-nz/angular-polaris" 7 | }, 8 | "author": { 9 | "name": "Maxime Rainville", 10 | "email": "maxime@rainville.me" 11 | }, 12 | "keywords": [ 13 | "angular" 14 | ], 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/syrp-nz/angular-polaris/issues" 18 | }, 19 | "module": "index.js", 20 | "typings": "angular-polaris.d.ts", 21 | "main": "angular-polaris.umd.js", 22 | "peerDependencies": { 23 | "@angular/common": "^4.0.0", 24 | "@angular/core": "^4.0.0", 25 | "@angular/forms": "^4.0.0", 26 | "@angular/platform-browser": "^4.0.0", 27 | "@angular/platform-browser-dynamic": "^4.0.0", 28 | "core-js": "^2.4.1", 29 | "quill": "^1.2.0", 30 | "rxjs": "^5.1.0", 31 | "zone.js": "^0.8.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/set'; 35 | 36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 37 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 38 | 39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 41 | 42 | 43 | /** Evergreen browsers require these. **/ 44 | import 'core-js/es6/reflect'; 45 | import 'core-js/es7/reflect'; 46 | 47 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 48 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 49 | 50 | 51 | 52 | /*************************************************************************************************** 53 | * Zone JS is required by Angular itself. 54 | */ 55 | import 'zone.js/dist/zone'; // Included with Angular CLI. 56 | 57 | 58 | 59 | /*************************************************************************************************** 60 | * APPLICATION IMPORTS 61 | */ 62 | 63 | /** 64 | * Date, currency, decimal and percent pipes. 65 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 66 | */ 67 | // import 'intl'; // Run `npm install --save intl`. 68 | /** 69 | * Need to import at least one locale-data with intl. 70 | */ 71 | // import 'intl/locale-data/jsonp/en'; 72 | -------------------------------------------------------------------------------- /src/prism-coy.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML 3 | * Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics); 4 | * @author Tim Shedor 5 | */ 6 | # 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | background: none; 11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 12 | text-align: left; 13 | white-space: pre; 14 | word-spacing: normal; 15 | word-break: normal; 16 | word-wrap: normal; 17 | line-height: 1.5; 18 | 19 | -moz-tab-size: 4; 20 | -o-tab-size: 4; 21 | tab-size: 4; 22 | 23 | -webkit-hyphens: none; 24 | -moz-hyphens: none; 25 | -ms-hyphens: none; 26 | hyphens: none; 27 | } 28 | 29 | /* Code blocks */ 30 | pre[class*="language-"] { 31 | position: relative; 32 | margin: .5em 0; 33 | box-shadow: -1px 0px 0px 0px #3f4eae, 0px 0px 0px 1px #dfdfdf; 34 | border-left: 10px solid #3f4eae; 35 | background-color: #fdfdfd; 36 | background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); 37 | background-size: 3em 3em; 38 | background-origin: content-box; 39 | overflow: visible; 40 | padding: 0; 41 | } 42 | 43 | code[class*="language"] { 44 | max-height: inherit; 45 | height: 100%; 46 | padding: 0 1em; 47 | display: block; 48 | overflow: auto; 49 | } 50 | 51 | /* Margin bottom to accomodate shadow */ 52 | :not(pre) > code[class*="language-"], 53 | pre[class*="language-"] { 54 | background-color: #fdfdfd; 55 | -webkit-box-sizing: border-box; 56 | -moz-box-sizing: border-box; 57 | box-sizing: border-box; 58 | margin-bottom: 1em; 59 | } 60 | 61 | /* Inline code */ 62 | :not(pre) > code[class*="language-"] { 63 | position: relative; 64 | padding: .2em; 65 | border-radius: 0.3em; 66 | color: #c92c2c; 67 | border: 1px solid rgba(0, 0, 0, 0.1); 68 | display: inline; 69 | white-space: normal; 70 | } 71 | 72 | pre[class*="language-"]:before, 73 | pre[class*="language-"]:after { 74 | content: ''; 75 | z-index: -2; 76 | display: block; 77 | position: absolute; 78 | bottom: 0.75em; 79 | left: 0.18em; 80 | width: 40%; 81 | height: 20%; 82 | max-height: 13em; 83 | box-shadow: 0px 13px 8px #979797; 84 | -webkit-transform: rotate(-2deg); 85 | -moz-transform: rotate(-2deg); 86 | -ms-transform: rotate(-2deg); 87 | -o-transform: rotate(-2deg); 88 | transform: rotate(-2deg); 89 | } 90 | 91 | :not(pre) > code[class*="language-"]:after, 92 | pre[class*="language-"]:after { 93 | right: 0.75em; 94 | left: auto; 95 | -webkit-transform: rotate(2deg); 96 | -moz-transform: rotate(2deg); 97 | -ms-transform: rotate(2deg); 98 | -o-transform: rotate(2deg); 99 | transform: rotate(2deg); 100 | } 101 | 102 | .token.comment, 103 | .token.block-comment, 104 | .token.prolog, 105 | .token.doctype, 106 | .token.cdata { 107 | color: #7D8B99; 108 | } 109 | 110 | .token.punctuation { 111 | color: #5F6364; 112 | } 113 | 114 | .token.property, 115 | .token.tag, 116 | .token.boolean, 117 | .token.number, 118 | .token.function-name, 119 | .token.constant, 120 | .token.symbol, 121 | .token.deleted { 122 | color: #c92c2c; 123 | } 124 | 125 | .token.selector, 126 | .token.attr-name, 127 | .token.string, 128 | .token.char, 129 | .token.function, 130 | .token.builtin, 131 | .token.inserted { 132 | color: #2f9c0a; 133 | } 134 | 135 | .token.operator, 136 | .token.entity, 137 | .token.url, 138 | .token.variable { 139 | color: #a67f59; 140 | background: rgba(255, 255, 255, 0.5); 141 | } 142 | 143 | .token.atrule, 144 | .token.attr-value, 145 | .token.keyword, 146 | .token.class-name { 147 | color: #1990b8; 148 | } 149 | 150 | .token.regex, 151 | .token.important { 152 | color: #e90; 153 | } 154 | 155 | .language-css .token.string, 156 | .style .token.string { 157 | color: #a67f59; 158 | background: rgba(255, 255, 255, 0.5); 159 | } 160 | 161 | .token.important { 162 | font-weight: normal; 163 | } 164 | 165 | .token.bold { 166 | font-weight: bold; 167 | } 168 | .token.italic { 169 | font-style: italic; 170 | } 171 | 172 | .token.entity { 173 | cursor: help; 174 | } 175 | 176 | .namespace { 177 | opacity: .7; 178 | } 179 | 180 | @media screen and (max-width: 767px) { 181 | pre[class*="language-"]:before, 182 | pre[class*="language-"]:after { 183 | bottom: 14px; 184 | box-shadow: none; 185 | } 186 | 187 | } 188 | 189 | /* Plugin styles */ 190 | .token.tab:not(:empty):before, 191 | .token.cr:before, 192 | .token.lf:before { 193 | color: #e0d7d1; 194 | } 195 | 196 | /* Plugin styles: Line Numbers */ 197 | pre[class*="language-"].line-numbers { 198 | padding-left: 0; 199 | } 200 | 201 | pre[class*="language-"].line-numbers code { 202 | padding-left: 3.8em; 203 | } 204 | 205 | pre[class*="language-"].line-numbers .line-numbers-rows { 206 | left: 0; 207 | } 208 | 209 | /* Plugin styles: Line Highlight */ 210 | pre[class*="language-"][data-line] { 211 | padding-top: 0; 212 | padding-bottom: 0; 213 | padding-left: 0; 214 | } 215 | pre[data-line] code { 216 | position: relative; 217 | padding-left: 4em; 218 | } 219 | pre .line-highlight { 220 | margin-top: 0; 221 | } 222 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.es5.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "es2015", 5 | "target": "es5", 6 | "baseUrl": ".", 7 | "stripInternal": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "moduleResolution": "node", 11 | "outDir": "../build", 12 | "rootDir": ".", 13 | "lib": [ 14 | "es2015", 15 | "dom" 16 | ], 17 | "skipLibCheck": true, 18 | "types": [], 19 | "paths": { 20 | "@angular/core": [ 21 | "../node_modules/@angular/core" 22 | ] 23 | }, 24 | 25 | "importHelpers": true, 26 | "newLine": "lf", 27 | "sourceMap": true, 28 | "inlineSources": true 29 | }, 30 | "angularCompilerOptions": { 31 | "annotateForClosureCompiler": true, 32 | "strictMetadataEmit": true, 33 | "skipTemplateCodegen": true, 34 | "flatModuleOutFile": "angular-polaris.js", 35 | "flatModuleId": "angular-polaris" 36 | }, 37 | "files": [ 38 | "./index.ts" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tools/gulp/inline-resources.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/filipesilva/angular-quickstart-lib/blob/master/inline-resources.js 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const glob = require('glob'); 8 | const sass = require('node-sass'); 9 | 10 | /** 11 | * Simple Promiseify function that takes a Node API and return a version that supports promises. 12 | * We use promises instead of synchronized functions to make the process less I/O bound and 13 | * faster. It also simplifies the code. 14 | */ 15 | function promiseify(fn) { 16 | return function () { 17 | const args = [].slice.call(arguments, 0); 18 | return new Promise((resolve, reject) => { 19 | fn.apply(this, args.concat([function (err, value) { 20 | if (err) { 21 | reject(err); 22 | } else { 23 | resolve(value); 24 | } 25 | }])); 26 | }); 27 | }; 28 | } 29 | 30 | const readFile = promiseify(fs.readFile); 31 | const writeFile = promiseify(fs.writeFile); 32 | 33 | /** 34 | * Inline resources in a tsc/ngc compilation. 35 | * @param projectPath {string} Path to the project. 36 | */ 37 | function inlineResources(projectPath) { 38 | 39 | // Match only TypeScript files in projectPath. 40 | const files = glob.sync('**/*.ts', {cwd: projectPath}); 41 | 42 | // For each file, inline the templates and styles under it and write the new file. 43 | return Promise.all(files.map(filePath => { 44 | const fullFilePath = path.join(projectPath, filePath); 45 | return readFile(fullFilePath, 'utf-8') 46 | .then(content => inlineResourcesFromString(content, url => { 47 | // Resolve the template url. 48 | return path.join(path.dirname(fullFilePath), url); 49 | })) 50 | .then(content => writeFile(fullFilePath, content)) 51 | .catch(err => { 52 | console.error('An error occured: ', err); 53 | }); 54 | })); 55 | } 56 | 57 | /** 58 | * Inline resources from a string content. 59 | * @param content {string} The source file's content. 60 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 61 | * @returns {string} The content with resources inlined. 62 | */ 63 | function inlineResourcesFromString(content, urlResolver) { 64 | // Curry through the inlining functions. 65 | return [ 66 | inlineTemplate, 67 | inlineStyle, 68 | removeModuleId 69 | ].reduce((content, fn) => fn(content, urlResolver), content); 70 | } 71 | 72 | /** 73 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and 74 | * replace with `template: ...` (with the content of the file included). 75 | * @param content {string} The source file's content. 76 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 77 | * @return {string} The content with all templates inlined. 78 | */ 79 | function inlineTemplate(content, urlResolver) { 80 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) { 81 | const templateFile = urlResolver(templateUrl); 82 | const templateContent = fs.readFileSync(templateFile, 'utf-8'); 83 | const shortenedTemplate = templateContent 84 | .replace(/([\n\r]\s*)+/gm, ' ') 85 | .replace(/"/g, '\\"'); 86 | return `template: "${shortenedTemplate}"`; 87 | }); 88 | } 89 | 90 | 91 | /** 92 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and 93 | * replace with `styles: [...]` (with the content of the file included). 94 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 95 | * @param content {string} The source file's content. 96 | * @return {string} The content with all styles inlined. 97 | */ 98 | function inlineStyle(content, urlResolver) { 99 | return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) { 100 | const urls = eval(styleUrls); 101 | return 'styles: [' 102 | + urls.map(styleUrl => { 103 | const styleFile = urlResolver(styleUrl); 104 | const originContent = fs.readFileSync(styleFile, 'utf-8'); 105 | const styleContent = styleFile.endsWith('.scss') ? buildSass(originContent, styleFile) : originContent; 106 | const shortenedStyle = styleContent 107 | .replace(/([\n\r]\s*)+/gm, ' ') 108 | .replace(/"/g, '\\"'); 109 | return `"${shortenedStyle}"`; 110 | }) 111 | .join(',\n') 112 | + ']'; 113 | }); 114 | } 115 | 116 | /** 117 | * build sass content to css 118 | * @param content {string} the css content 119 | * @param sourceFile {string} the scss file sourceFile 120 | * @return {string} the generated css, empty string if error occured 121 | */ 122 | function buildSass(content, sourceFile) { 123 | try { 124 | const result = sass.renderSync({data: content}); 125 | return result.css.toString() 126 | } catch (e) { 127 | console.error('\x1b[41m'); 128 | console.error('at ' + sourceFile + ':' + e.line + ":" + e.column); 129 | console.error(e.formatted); 130 | console.error('\x1b[0m'); 131 | return ""; 132 | } 133 | } 134 | 135 | /** 136 | * Remove every mention of `moduleId: module.id`. 137 | * @param content {string} The source file's content. 138 | * @returns {string} The content with all moduleId: mentions removed. 139 | */ 140 | function removeModuleId(content) { 141 | return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, ''); 142 | } 143 | 144 | module.exports = inlineResources; 145 | module.exports.inlineResourcesFromString = inlineResourcesFromString; 146 | 147 | // Run inlineResources if module is being called directly from the CLI with arguments. 148 | if (require.main === module && process.argv.length > 2) { 149 | console.log('Inlining resources from project:', process.argv[2]); 150 | return inlineResources(process.argv[2]); 151 | } 152 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | }, 20 | "exclude": [ 21 | "./node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [ 16 | true, 17 | "rxjs" 18 | ], 19 | "import-spacing": true, 20 | "indent": [ 21 | true, 22 | "spaces" 23 | ], 24 | "interface-over-type-literal": true, 25 | "label-position": true, 26 | "max-line-length": [ 27 | true, 28 | 140 29 | ], 30 | "member-access": false, 31 | "member-ordering": [ 32 | true, 33 | "static-before-instance", 34 | "variables-before-functions" 35 | ], 36 | "no-arg": true, 37 | "no-bitwise": true, 38 | "no-console": [ 39 | true, 40 | "debug", 41 | "info", 42 | "time", 43 | "timeEnd", 44 | "trace" 45 | ], 46 | "no-construct": true, 47 | "no-debugger": true, 48 | "no-empty": false, 49 | "no-empty-interface": true, 50 | "no-eval": true, 51 | "no-inferrable-types": [ 52 | true, 53 | "ignore-params" 54 | ], 55 | "no-shadowed-variable": true, 56 | "no-string-literal": false, 57 | "no-string-throw": true, 58 | "no-switch-case-fall-through": true, 59 | "no-trailing-whitespace": true, 60 | "no-unused-expression": true, 61 | "no-use-before-declare": true, 62 | "no-var-keyword": true, 63 | "object-literal-sort-keys": false, 64 | "one-line": [ 65 | true, 66 | "check-open-brace", 67 | "check-catch", 68 | "check-else", 69 | "check-whitespace" 70 | ], 71 | "prefer-const": true, 72 | "quotemark": [ 73 | true, 74 | "single" 75 | ], 76 | "radix": true, 77 | "semicolon": [ 78 | "always" 79 | ], 80 | "triple-equals": [ 81 | true, 82 | "allow-null-check" 83 | ], 84 | "typedef-whitespace": [ 85 | true, 86 | { 87 | "call-signature": "nospace", 88 | "index-signature": "nospace", 89 | "parameter": "nospace", 90 | "property-declaration": "nospace", 91 | "variable-declaration": "nospace" 92 | } 93 | ], 94 | "typeof-compare": true, 95 | "unified-signatures": true, 96 | "variable-name": false, 97 | "whitespace": [ 98 | true, 99 | "check-branch", 100 | "check-decl", 101 | "check-operator", 102 | "check-separator", 103 | "check-type" 104 | ], 105 | "directive-selector": [ 106 | true, 107 | "attribute", 108 | "plrs", 109 | "camelCase" 110 | ], 111 | "component-selector": [ 112 | true, 113 | "element", 114 | "plrs", 115 | "kebab-case" 116 | ], 117 | "use-input-property-decorator": true, 118 | "use-output-property-decorator": true, 119 | "use-host-property-decorator": true, 120 | "no-input-rename": true, 121 | "no-output-rename": true, 122 | "use-life-cycle-interface": true, 123 | "use-pipe-transform-interface": true, 124 | "component-class-suffix": true, 125 | "directive-class-suffix": true, 126 | "no-access-missing-member": true, 127 | "templates-use-public": true, 128 | "invoke-injectable": true 129 | } 130 | } 131 | --------------------------------------------------------------------------------