diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..77989696 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# pow-style + +root = true + +[*] +charset = utf-8 +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.{ts,html}] +max_line_length = 120 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d541d436 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.idea +lib/ +ng2-material/**/*.css +ng2-material/**/*.css.map +ng2-material/**/*.js +ng2-material/**/*.js.map +ng2-material/**/*.d.ts diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..952f2cef --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,88 @@ +module.exports = function (grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + sourceRoot: 'ng2-material', + clean: [ + "lib/", + "<%- sourceRoot %>/**/*.js", + "<%- sourceRoot %>/**/*.js.map", + "<%- sourceRoot %>/**/*.css", + "<%- sourceRoot %>/**/*.css.map" + ], + notify: { + options: {title: 'Material for Angular2'}, + styles: {options: {message: 'Styles Compiled.'}}, + source: {options: {message: 'Source Compiled.'}} + }, + ts: { + source: { + tsconfig: true + } + }, + sass: { + dist: { + files: [{ + expand: true, + cwd: '<%- sourceRoot %>', + dest: '<%- sourceRoot %>', + src: ["<%- sourceRoot %>/all.scss", "<%- sourceRoot %>/**/*.scss"], + ext: '.css' + }] + } + }, + 'http-server': { + dev: { + root: 'public', + port: 8282, + host: 'localhost', + ext: 'html', + runInBackground: true, + https: true, + openBrowser: true + } + }, + watch: { + sass: { + files: ['<%- sourceRoot %>/**/*.scss'], + tasks: ['sass', 'notify:styles'] + }, + ts: { + files: ['<%- sourceRoot %>/**/*.ts'], + tasks: ['ts', 'notify:source'] + } + } + }); + + grunt.loadNpmTasks('grunt-http-server'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-sass'); + grunt.loadNpmTasks('grunt-notify'); + grunt.loadNpmTasks('grunt-ts'); + grunt.registerTask('default', ['ts', 'sass']); + grunt.registerTask('develop-ide', ['default', 'http-server']); + grunt.registerTask('develop', ['develop-ide', 'watch']); + + + grunt.registerTask('dist-bundle', 'Build a single-file javascript output.', function () { + var done = this.async(); + var Builder = require('systemjs-builder'); + var builder = new Builder('./', './config.js'); + builder + .bundle('ng2-material', 'lib/ng2-material/ng2-material.js', { + minify: false, + sourceMaps: true + }) + .then(function () { + return builder.bundle('ng2-material', 'lib/ng2-material/ng2-material.min.js', { + minify: true, + sourceMaps: true + }); + }) + .then(function () { + done(); + }); + }); + + +}; diff --git a/README.md b/README.md new file mode 100644 index 00000000..b87b1e23 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +Material Design for Angular2 +--- + +This project is intended to be a placeholder for material design components in Angular2 until an official +implementation is provided. + +These components are based very heavily on the components and styles defined in the +[Angular Material](https://github.com/angular/material) and [Angular 2](https://github.com/angular/angular) projects. + + + +## License + +### [Angular Material](https://github.com/angular/material) + + - scss and source file reference for angular2 component implementations and services + - layout and typography styles (layout="row",layout-align="center center",...) + +License: [MIT](https://github.com/angular/material/blob/master/LICENSE) + +### [Angular 2.0.0-alpha.48](https://github.com/angular/angular) + + - scss utilities for defining default material theme and theme function + +License: [Apache2](https://github.com/angular/angular/blob/master/LICENSE) diff --git a/config.js b/config.js new file mode 100644 index 00000000..d23facf9 --- /dev/null +++ b/config.js @@ -0,0 +1,23 @@ +System.config({ + baseUrl: './', + transpiler: 'typescript', + typescriptOptions: { + resolveTypings: true, + emitDecoratorMetadata: true, + sourceMap: true, + inlineSourceMap: false + }, + packages: { + 'ng2-material': { + main: './all.ts', + defaultExtension: 'ts' + } + }, + map: { + typescript: './node_modules/typescript/lib/typescript.js' + }, + paths: { + "angular2/*": "./node_modules/angular2/bundles/angular2.dev.js" + }, + defaultJSExtensions: true +}); diff --git a/font/MaterialIcons-Regular.eot b/font/MaterialIcons-Regular.eot new file mode 100644 index 00000000..bf67d48b Binary files /dev/null and b/font/MaterialIcons-Regular.eot differ diff --git a/font/MaterialIcons-Regular.ijmap b/font/MaterialIcons-Regular.ijmap new file mode 100644 index 00000000..e1df40d1 --- /dev/null +++ b/font/MaterialIcons-Regular.ijmap @@ -0,0 +1 @@ +{"icons":{"e84d":{"name":"3d Rotation"},"eb3b":{"name":"Ac Unit"},"e190":{"name":"Access Alarm"},"e191":{"name":"Access Alarms"},"e192":{"name":"Access Time"},"e84e":{"name":"Accessibility"},"e914":{"name":"Accessible"},"e84f":{"name":"Account Balance"},"e850":{"name":"Account Balance Wallet"},"e851":{"name":"Account Box"},"e853":{"name":"Account Circle"},"e60e":{"name":"Adb"},"e145":{"name":"Add"},"e439":{"name":"Add A Photo"},"e193":{"name":"Add Alarm"},"e003":{"name":"Add Alert"},"e146":{"name":"Add Box"},"e147":{"name":"Add Circle"},"e148":{"name":"Add Circle Outline"},"e567":{"name":"Add Location"},"e854":{"name":"Add Shopping Cart"},"e39d":{"name":"Add To Photos"},"e05c":{"name":"Add To Queue"},"e39e":{"name":"Adjust"},"e630":{"name":"Airline Seat Flat"},"e631":{"name":"Airline Seat Flat Angled"},"e632":{"name":"Airline Seat Individual Suite"},"e633":{"name":"Airline Seat Legroom Extra"},"e634":{"name":"Airline Seat Legroom Normal"},"e635":{"name":"Airline Seat Legroom Reduced"},"e636":{"name":"Airline Seat Recline Extra"},"e637":{"name":"Airline Seat Recline Normal"},"e195":{"name":"Airplanemode Active"},"e194":{"name":"Airplanemode Inactive"},"e055":{"name":"Airplay"},"eb3c":{"name":"Airport Shuttle"},"e855":{"name":"Alarm"},"e856":{"name":"Alarm Add"},"e857":{"name":"Alarm Off"},"e858":{"name":"Alarm On"},"e019":{"name":"Album"},"eb3d":{"name":"All Inclusive"},"e90b":{"name":"All Out"},"e859":{"name":"Android"},"e85a":{"name":"Announcement"},"e5c3":{"name":"Apps"},"e149":{"name":"Archive"},"e5c4":{"name":"Arrow Back"},"e5db":{"name":"Arrow Downward"},"e5c5":{"name":"Arrow Drop Down"},"e5c6":{"name":"Arrow Drop Down Circle"},"e5c7":{"name":"Arrow Drop Up"},"e5c8":{"name":"Arrow Forward"},"e5d8":{"name":"Arrow Upward"},"e060":{"name":"Art Track"},"e85b":{"name":"Aspect Ratio"},"e85c":{"name":"Assessment"},"e85d":{"name":"Assignment"},"e85e":{"name":"Assignment Ind"},"e85f":{"name":"Assignment Late"},"e860":{"name":"Assignment Return"},"e861":{"name":"Assignment Returned"},"e862":{"name":"Assignment Turned In"},"e39f":{"name":"Assistant"},"e3a0":{"name":"Assistant Photo"},"e226":{"name":"Attach File"},"e227":{"name":"Attach Money"},"e2bc":{"name":"Attachment"},"e3a1":{"name":"Audiotrack"},"e863":{"name":"Autorenew"},"e01b":{"name":"Av Timer"},"e14a":{"name":"Backspace"},"e864":{"name":"Backup"},"e19c":{"name":"Battery Alert"},"e1a3":{"name":"Battery Charging Full"},"e1a4":{"name":"Battery Full"},"e1a5":{"name":"Battery Std"},"e1a6":{"name":"Battery Unknown"},"eb3e":{"name":"Beach Access"},"e52d":{"name":"Beenhere"},"e14b":{"name":"Block"},"e1a7":{"name":"Bluetooth"},"e60f":{"name":"Bluetooth Audio"},"e1a8":{"name":"Bluetooth Connected"},"e1a9":{"name":"Bluetooth Disabled"},"e1aa":{"name":"Bluetooth Searching"},"e3a2":{"name":"Blur Circular"},"e3a3":{"name":"Blur Linear"},"e3a4":{"name":"Blur Off"},"e3a5":{"name":"Blur On"},"e865":{"name":"Book"},"e866":{"name":"Bookmark"},"e867":{"name":"Bookmark Border"},"e228":{"name":"Border All"},"e229":{"name":"Border Bottom"},"e22a":{"name":"Border Clear"},"e22b":{"name":"Border Color"},"e22c":{"name":"Border Horizontal"},"e22d":{"name":"Border Inner"},"e22e":{"name":"Border Left"},"e22f":{"name":"Border Outer"},"e230":{"name":"Border Right"},"e231":{"name":"Border Style"},"e232":{"name":"Border Top"},"e233":{"name":"Border Vertical"},"e3a6":{"name":"Brightness 1"},"e3a7":{"name":"Brightness 2"},"e3a8":{"name":"Brightness 3"},"e3a9":{"name":"Brightness 4"},"e3aa":{"name":"Brightness 5"},"e3ab":{"name":"Brightness 6"},"e3ac":{"name":"Brightness 7"},"e1ab":{"name":"Brightness Auto"},"e1ac":{"name":"Brightness High"},"e1ad":{"name":"Brightness Low"},"e1ae":{"name":"Brightness Medium"},"e3ad":{"name":"Broken Image"},"e3ae":{"name":"Brush"},"e868":{"name":"Bug Report"},"e869":{"name":"Build"},"e0af":{"name":"Business"},"eb3f":{"name":"Business Center"},"e86a":{"name":"Cached"},"e7e9":{"name":"Cake"},"e0b0":{"name":"Call"},"e0b1":{"name":"Call End"},"e0b2":{"name":"Call Made"},"e0b3":{"name":"Call Merge"},"e0b4":{"name":"Call Missed"},"e0e4":{"name":"Call Missed Outgoing"},"e0b5":{"name":"Call Received"},"e0b6":{"name":"Call Split"},"e3af":{"name":"Camera"},"e3b0":{"name":"Camera Alt"},"e8fc":{"name":"Camera Enhance"},"e3b1":{"name":"Camera Front"},"e3b2":{"name":"Camera Rear"},"e3b3":{"name":"Camera Roll"},"e5c9":{"name":"Cancel"},"e8f6":{"name":"Card Giftcard"},"e8f7":{"name":"Card Membership"},"e8f8":{"name":"Card Travel"},"eb40":{"name":"Casino"},"e307":{"name":"Cast"},"e308":{"name":"Cast Connected"},"e3b4":{"name":"Center Focus Strong"},"e3b5":{"name":"Center Focus Weak"},"e86b":{"name":"Change History"},"e0b7":{"name":"Chat"},"e0ca":{"name":"Chat Bubble"},"e0cb":{"name":"Chat Bubble Outline"},"e5ca":{"name":"Check"},"e834":{"name":"Check Box"},"e835":{"name":"Check Box Outline Blank"},"e86c":{"name":"Check Circle"},"e5cb":{"name":"Chevron Left"},"e5cc":{"name":"Chevron Right"},"eb41":{"name":"Child Care"},"eb42":{"name":"Child Friendly"},"e86d":{"name":"Chrome Reader Mode"},"e86e":{"name":"Class"},"e14c":{"name":"Clear"},"e0b8":{"name":"Clear All"},"e5cd":{"name":"Close"},"e01c":{"name":"Closed Caption"},"e2bd":{"name":"Cloud"},"e2be":{"name":"Cloud Circle"},"e2bf":{"name":"Cloud Done"},"e2c0":{"name":"Cloud Download"},"e2c1":{"name":"Cloud Off"},"e2c2":{"name":"Cloud Queue"},"e2c3":{"name":"Cloud Upload"},"e86f":{"name":"Code"},"e3b6":{"name":"Collections"},"e431":{"name":"Collections Bookmark"},"e3b7":{"name":"Color Lens"},"e3b8":{"name":"Colorize"},"e0b9":{"name":"Comment"},"e3b9":{"name":"Compare"},"e915":{"name":"Compare Arrows"},"e30a":{"name":"Computer"},"e638":{"name":"Confirmation Number"},"e0d0":{"name":"Contact Mail"},"e0cf":{"name":"Contact Phone"},"e0ba":{"name":"Contacts"},"e14d":{"name":"Content Copy"},"e14e":{"name":"Content Cut"},"e14f":{"name":"Content Paste"},"e3ba":{"name":"Control Point"},"e3bb":{"name":"Control Point Duplicate"},"e90c":{"name":"Copyright"},"e150":{"name":"Create"},"e2cc":{"name":"Create New Folder"},"e870":{"name":"Credit Card"},"e3be":{"name":"Crop"},"e3bc":{"name":"Crop 16 9"},"e3bd":{"name":"Crop 3 2"},"e3bf":{"name":"Crop 5 4"},"e3c0":{"name":"Crop 7 5"},"e3c1":{"name":"Crop Din"},"e3c2":{"name":"Crop Free"},"e3c3":{"name":"Crop Landscape"},"e3c4":{"name":"Crop Original"},"e3c5":{"name":"Crop Portrait"},"e437":{"name":"Crop Rotate"},"e3c6":{"name":"Crop Square"},"e871":{"name":"Dashboard"},"e1af":{"name":"Data Usage"},"e916":{"name":"Date Range"},"e3c7":{"name":"Dehaze"},"e872":{"name":"Delete"},"e873":{"name":"Description"},"e30b":{"name":"Desktop Mac"},"e30c":{"name":"Desktop Windows"},"e3c8":{"name":"Details"},"e30d":{"name":"Developer Board"},"e1b0":{"name":"Developer Mode"},"e335":{"name":"Device Hub"},"e1b1":{"name":"Devices"},"e337":{"name":"Devices Other"},"e0bb":{"name":"Dialer Sip"},"e0bc":{"name":"Dialpad"},"e52e":{"name":"Directions"},"e52f":{"name":"Directions Bike"},"e532":{"name":"Directions Boat"},"e530":{"name":"Directions Bus"},"e531":{"name":"Directions Car"},"e534":{"name":"Directions Railway"},"e566":{"name":"Directions Run"},"e533":{"name":"Directions Subway"},"e535":{"name":"Directions Transit"},"e536":{"name":"Directions Walk"},"e610":{"name":"Disc Full"},"e875":{"name":"Dns"},"e612":{"name":"Do Not Disturb"},"e611":{"name":"Do Not Disturb Alt"},"e30e":{"name":"Dock"},"e7ee":{"name":"Domain"},"e876":{"name":"Done"},"e877":{"name":"Done All"},"e917":{"name":"Donut Large"},"e918":{"name":"Donut Small"},"e151":{"name":"Drafts"},"e25d":{"name":"Drag Handle"},"e613":{"name":"Drive Eta"},"e1b2":{"name":"Dvr"},"e3c9":{"name":"Edit"},"e568":{"name":"Edit Location"},"e8fb":{"name":"Eject"},"e0be":{"name":"Email"},"e63f":{"name":"Enhanced Encryption"},"e01d":{"name":"Equalizer"},"e000":{"name":"Error"},"e001":{"name":"Error Outline"},"e878":{"name":"Event"},"e614":{"name":"Event Available"},"e615":{"name":"Event Busy"},"e616":{"name":"Event Note"},"e903":{"name":"Event Seat"},"e879":{"name":"Exit To App"},"e5ce":{"name":"Expand Less"},"e5cf":{"name":"Expand More"},"e01e":{"name":"Explicit"},"e87a":{"name":"Explore"},"e3ca":{"name":"Exposure"},"e3cb":{"name":"Exposure Neg 1"},"e3cc":{"name":"Exposure Neg 2"},"e3cd":{"name":"Exposure Plus 1"},"e3ce":{"name":"Exposure Plus 2"},"e3cf":{"name":"Exposure Zero"},"e87b":{"name":"Extension"},"e87c":{"name":"Face"},"e01f":{"name":"Fast Forward"},"e020":{"name":"Fast Rewind"},"e87d":{"name":"Favorite"},"e87e":{"name":"Favorite Border"},"e87f":{"name":"Feedback"},"e05d":{"name":"Fiber Dvr"},"e061":{"name":"Fiber Manual Record"},"e05e":{"name":"Fiber New"},"e06a":{"name":"Fiber Pin"},"e062":{"name":"Fiber Smart Record"},"e2c4":{"name":"File Download"},"e2c6":{"name":"File Upload"},"e3d3":{"name":"Filter"},"e3d0":{"name":"Filter 1"},"e3d1":{"name":"Filter 2"},"e3d2":{"name":"Filter 3"},"e3d4":{"name":"Filter 4"},"e3d5":{"name":"Filter 5"},"e3d6":{"name":"Filter 6"},"e3d7":{"name":"Filter 7"},"e3d8":{"name":"Filter 8"},"e3d9":{"name":"Filter 9"},"e3da":{"name":"Filter 9 Plus"},"e3db":{"name":"Filter B And W"},"e3dc":{"name":"Filter Center Focus"},"e3dd":{"name":"Filter Drama"},"e3de":{"name":"Filter Frames"},"e3df":{"name":"Filter Hdr"},"e152":{"name":"Filter List"},"e3e0":{"name":"Filter None"},"e3e2":{"name":"Filter Tilt Shift"},"e3e3":{"name":"Filter Vintage"},"e880":{"name":"Find In Page"},"e881":{"name":"Find Replace"},"e90d":{"name":"Fingerprint"},"eb43":{"name":"Fitness Center"},"e153":{"name":"Flag"},"e3e4":{"name":"Flare"},"e3e5":{"name":"Flash Auto"},"e3e6":{"name":"Flash Off"},"e3e7":{"name":"Flash On"},"e539":{"name":"Flight"},"e904":{"name":"Flight Land"},"e905":{"name":"Flight Takeoff"},"e3e8":{"name":"Flip"},"e882":{"name":"Flip To Back"},"e883":{"name":"Flip To Front"},"e2c7":{"name":"Folder"},"e2c8":{"name":"Folder Open"},"e2c9":{"name":"Folder Shared"},"e617":{"name":"Folder Special"},"e167":{"name":"Font Download"},"e234":{"name":"Format Align Center"},"e235":{"name":"Format Align Justify"},"e236":{"name":"Format Align Left"},"e237":{"name":"Format Align Right"},"e238":{"name":"Format Bold"},"e239":{"name":"Format Clear"},"e23a":{"name":"Format Color Fill"},"e23b":{"name":"Format Color Reset"},"e23c":{"name":"Format Color Text"},"e23d":{"name":"Format Indent Decrease"},"e23e":{"name":"Format Indent Increase"},"e23f":{"name":"Format Italic"},"e240":{"name":"Format Line Spacing"},"e241":{"name":"Format List Bulleted"},"e242":{"name":"Format List Numbered"},"e243":{"name":"Format Paint"},"e244":{"name":"Format Quote"},"e25e":{"name":"Format Shapes"},"e245":{"name":"Format Size"},"e246":{"name":"Format Strikethrough"},"e247":{"name":"Format Textdirection L To R"},"e248":{"name":"Format Textdirection R To L"},"e249":{"name":"Format Underlined"},"e0bf":{"name":"Forum"},"e154":{"name":"Forward"},"e056":{"name":"Forward 10"},"e057":{"name":"Forward 30"},"e058":{"name":"Forward 5"},"eb44":{"name":"Free Breakfast"},"e5d0":{"name":"Fullscreen"},"e5d1":{"name":"Fullscreen Exit"},"e24a":{"name":"Functions"},"e30f":{"name":"Gamepad"},"e021":{"name":"Games"},"e90e":{"name":"Gavel"},"e155":{"name":"Gesture"},"e884":{"name":"Get App"},"e908":{"name":"Gif"},"eb45":{"name":"Golf Course"},"e1b3":{"name":"Gps Fixed"},"e1b4":{"name":"Gps Not Fixed"},"e1b5":{"name":"Gps Off"},"e885":{"name":"Grade"},"e3e9":{"name":"Gradient"},"e3ea":{"name":"Grain"},"e1b8":{"name":"Graphic Eq"},"e3eb":{"name":"Grid Off"},"e3ec":{"name":"Grid On"},"e7ef":{"name":"Group"},"e7f0":{"name":"Group Add"},"e886":{"name":"Group Work"},"e052":{"name":"Hd"},"e3ed":{"name":"Hdr Off"},"e3ee":{"name":"Hdr On"},"e3f1":{"name":"Hdr Strong"},"e3f2":{"name":"Hdr Weak"},"e310":{"name":"Headset"},"e311":{"name":"Headset Mic"},"e3f3":{"name":"Healing"},"e023":{"name":"Hearing"},"e887":{"name":"Help"},"e8fd":{"name":"Help Outline"},"e024":{"name":"High Quality"},"e25f":{"name":"Highlight"},"e888":{"name":"Highlight Off"},"e889":{"name":"History"},"e88a":{"name":"Home"},"eb46":{"name":"Hot Tub"},"e53a":{"name":"Hotel"},"e88b":{"name":"Hourglass Empty"},"e88c":{"name":"Hourglass Full"},"e902":{"name":"Http"},"e88d":{"name":"Https"},"e3f4":{"name":"Image"},"e3f5":{"name":"Image Aspect Ratio"},"e0e0":{"name":"Import Contacts"},"e0c3":{"name":"Import Export"},"e912":{"name":"Important Devices"},"e156":{"name":"Inbox"},"e909":{"name":"Indeterminate Check Box"},"e88e":{"name":"Info"},"e88f":{"name":"Info Outline"},"e890":{"name":"Input"},"e24b":{"name":"Insert Chart"},"e24c":{"name":"Insert Comment"},"e24d":{"name":"Insert Drive File"},"e24e":{"name":"Insert Emoticon"},"e24f":{"name":"Insert Invitation"},"e250":{"name":"Insert Link"},"e251":{"name":"Insert Photo"},"e891":{"name":"Invert Colors"},"e0c4":{"name":"Invert Colors Off"},"e3f6":{"name":"Iso"},"e312":{"name":"Keyboard"},"e313":{"name":"Keyboard Arrow Down"},"e314":{"name":"Keyboard Arrow Left"},"e315":{"name":"Keyboard Arrow Right"},"e316":{"name":"Keyboard Arrow Up"},"e317":{"name":"Keyboard Backspace"},"e318":{"name":"Keyboard Capslock"},"e31a":{"name":"Keyboard Hide"},"e31b":{"name":"Keyboard Return"},"e31c":{"name":"Keyboard Tab"},"e31d":{"name":"Keyboard Voice"},"eb47":{"name":"Kitchen"},"e892":{"name":"Label"},"e893":{"name":"Label Outline"},"e3f7":{"name":"Landscape"},"e894":{"name":"Language"},"e31e":{"name":"Laptop"},"e31f":{"name":"Laptop Chromebook"},"e320":{"name":"Laptop Mac"},"e321":{"name":"Laptop Windows"},"e895":{"name":"Launch"},"e53b":{"name":"Layers"},"e53c":{"name":"Layers Clear"},"e3f8":{"name":"Leak Add"},"e3f9":{"name":"Leak Remove"},"e3fa":{"name":"Lens"},"e02e":{"name":"Library Add"},"e02f":{"name":"Library Books"},"e030":{"name":"Library Music"},"e90f":{"name":"Lightbulb Outline"},"e919":{"name":"Line Style"},"e91a":{"name":"Line Weight"},"e260":{"name":"Linear Scale"},"e157":{"name":"Link"},"e438":{"name":"Linked Camera"},"e896":{"name":"List"},"e0c6":{"name":"Live Help"},"e639":{"name":"Live Tv"},"e53f":{"name":"Local Activity"},"e53d":{"name":"Local Airport"},"e53e":{"name":"Local Atm"},"e540":{"name":"Local Bar"},"e541":{"name":"Local Cafe"},"e542":{"name":"Local Car Wash"},"e543":{"name":"Local Convenience Store"},"e556":{"name":"Local Dining"},"e544":{"name":"Local Drink"},"e545":{"name":"Local Florist"},"e546":{"name":"Local Gas Station"},"e547":{"name":"Local Grocery Store"},"e548":{"name":"Local Hospital"},"e549":{"name":"Local Hotel"},"e54a":{"name":"Local Laundry Service"},"e54b":{"name":"Local Library"},"e54c":{"name":"Local Mall"},"e54d":{"name":"Local Movies"},"e54e":{"name":"Local Offer"},"e54f":{"name":"Local Parking"},"e550":{"name":"Local Pharmacy"},"e551":{"name":"Local Phone"},"e552":{"name":"Local Pizza"},"e553":{"name":"Local Play"},"e554":{"name":"Local Post Office"},"e555":{"name":"Local Printshop"},"e557":{"name":"Local See"},"e558":{"name":"Local Shipping"},"e559":{"name":"Local Taxi"},"e7f1":{"name":"Location City"},"e1b6":{"name":"Location Disabled"},"e0c7":{"name":"Location Off"},"e0c8":{"name":"Location On"},"e1b7":{"name":"Location Searching"},"e897":{"name":"Lock"},"e898":{"name":"Lock Open"},"e899":{"name":"Lock Outline"},"e3fc":{"name":"Looks"},"e3fb":{"name":"Looks 3"},"e3fd":{"name":"Looks 4"},"e3fe":{"name":"Looks 5"},"e3ff":{"name":"Looks 6"},"e400":{"name":"Looks One"},"e401":{"name":"Looks Two"},"e028":{"name":"Loop"},"e402":{"name":"Loupe"},"e89a":{"name":"Loyalty"},"e158":{"name":"Mail"},"e0e1":{"name":"Mail Outline"},"e55b":{"name":"Map"},"e159":{"name":"Markunread"},"e89b":{"name":"Markunread Mailbox"},"e322":{"name":"Memory"},"e5d2":{"name":"Menu"},"e252":{"name":"Merge Type"},"e0c9":{"name":"Message"},"e029":{"name":"Mic"},"e02a":{"name":"Mic None"},"e02b":{"name":"Mic Off"},"e618":{"name":"Mms"},"e253":{"name":"Mode Comment"},"e254":{"name":"Mode Edit"},"e25c":{"name":"Money Off"},"e403":{"name":"Monochrome Photos"},"e7f2":{"name":"Mood"},"e7f3":{"name":"Mood Bad"},"e619":{"name":"More"},"e5d3":{"name":"More Horiz"},"e5d4":{"name":"More Vert"},"e91b":{"name":"Motorcycle"},"e323":{"name":"Mouse"},"e168":{"name":"Move To Inbox"},"e02c":{"name":"Movie"},"e404":{"name":"Movie Creation"},"e43a":{"name":"Movie Filter"},"e405":{"name":"Music Note"},"e063":{"name":"Music Video"},"e55c":{"name":"My Location"},"e406":{"name":"Nature"},"e407":{"name":"Nature People"},"e408":{"name":"Navigate Before"},"e409":{"name":"Navigate Next"},"e55d":{"name":"Navigation"},"e569":{"name":"Near Me"},"e1b9":{"name":"Network Cell"},"e640":{"name":"Network Check"},"e61a":{"name":"Network Locked"},"e1ba":{"name":"Network Wifi"},"e031":{"name":"New Releases"},"e16a":{"name":"Next Week"},"e1bb":{"name":"Nfc"},"e641":{"name":"No Encryption"},"e0cc":{"name":"No Sim"},"e033":{"name":"Not Interested"},"e89c":{"name":"Note Add"},"e7f4":{"name":"Notifications"},"e7f7":{"name":"Notifications Active"},"e7f5":{"name":"Notifications None"},"e7f6":{"name":"Notifications Off"},"e7f8":{"name":"Notifications Paused"},"e90a":{"name":"Offline Pin"},"e63a":{"name":"Ondemand Video"},"e91c":{"name":"Opacity"},"e89d":{"name":"Open In Browser"},"e89e":{"name":"Open In New"},"e89f":{"name":"Open With"},"e7f9":{"name":"Pages"},"e8a0":{"name":"Pageview"},"e40a":{"name":"Palette"},"e925":{"name":"Pan Tool"},"e40b":{"name":"Panorama"},"e40c":{"name":"Panorama Fish Eye"},"e40d":{"name":"Panorama Horizontal"},"e40e":{"name":"Panorama Vertical"},"e40f":{"name":"Panorama Wide Angle"},"e7fa":{"name":"Party Mode"},"e034":{"name":"Pause"},"e035":{"name":"Pause Circle Filled"},"e036":{"name":"Pause Circle Outline"},"e8a1":{"name":"Payment"},"e7fb":{"name":"People"},"e7fc":{"name":"People Outline"},"e8a2":{"name":"Perm Camera Mic"},"e8a3":{"name":"Perm Contact Calendar"},"e8a4":{"name":"Perm Data Setting"},"e8a5":{"name":"Perm Device Information"},"e8a6":{"name":"Perm Identity"},"e8a7":{"name":"Perm Media"},"e8a8":{"name":"Perm Phone Msg"},"e8a9":{"name":"Perm Scan Wifi"},"e7fd":{"name":"Person"},"e7fe":{"name":"Person Add"},"e7ff":{"name":"Person Outline"},"e55a":{"name":"Person Pin"},"e56a":{"name":"Person Pin Circle"},"e63b":{"name":"Personal Video"},"e91d":{"name":"Pets"},"e0cd":{"name":"Phone"},"e324":{"name":"Phone Android"},"e61b":{"name":"Phone Bluetooth Speaker"},"e61c":{"name":"Phone Forwarded"},"e61d":{"name":"Phone In Talk"},"e325":{"name":"Phone Iphone"},"e61e":{"name":"Phone Locked"},"e61f":{"name":"Phone Missed"},"e620":{"name":"Phone Paused"},"e326":{"name":"Phonelink"},"e0db":{"name":"Phonelink Erase"},"e0dc":{"name":"Phonelink Lock"},"e327":{"name":"Phonelink Off"},"e0dd":{"name":"Phonelink Ring"},"e0de":{"name":"Phonelink Setup"},"e410":{"name":"Photo"},"e411":{"name":"Photo Album"},"e412":{"name":"Photo Camera"},"e43b":{"name":"Photo Filter"},"e413":{"name":"Photo Library"},"e432":{"name":"Photo Size Select Actual"},"e433":{"name":"Photo Size Select Large"},"e434":{"name":"Photo Size Select Small"},"e415":{"name":"Picture As Pdf"},"e8aa":{"name":"Picture In Picture"},"e911":{"name":"Picture In Picture Alt"},"e55e":{"name":"Pin Drop"},"e55f":{"name":"Place"},"e037":{"name":"Play Arrow"},"e038":{"name":"Play Circle Filled"},"e039":{"name":"Play Circle Outline"},"e906":{"name":"Play For Work"},"e03b":{"name":"Playlist Add"},"e065":{"name":"Playlist Add Check"},"e05f":{"name":"Playlist Play"},"e800":{"name":"Plus One"},"e801":{"name":"Poll"},"e8ab":{"name":"Polymer"},"eb48":{"name":"Pool"},"e0ce":{"name":"Portable Wifi Off"},"e416":{"name":"Portrait"},"e63c":{"name":"Power"},"e336":{"name":"Power Input"},"e8ac":{"name":"Power Settings New"},"e91e":{"name":"Pregnant Woman"},"e0df":{"name":"Present To All"},"e8ad":{"name":"Print"},"e80b":{"name":"Public"},"e255":{"name":"Publish"},"e8ae":{"name":"Query Builder"},"e8af":{"name":"Question Answer"},"e03c":{"name":"Queue"},"e03d":{"name":"Queue Music"},"e066":{"name":"Queue Play Next"},"e03e":{"name":"Radio"},"e837":{"name":"Radio Button Checked"},"e836":{"name":"Radio Button Unchecked"},"e560":{"name":"Rate Review"},"e8b0":{"name":"Receipt"},"e03f":{"name":"Recent Actors"},"e91f":{"name":"Record Voice Over"},"e8b1":{"name":"Redeem"},"e15a":{"name":"Redo"},"e5d5":{"name":"Refresh"},"e15b":{"name":"Remove"},"e15c":{"name":"Remove Circle"},"e15d":{"name":"Remove Circle Outline"},"e067":{"name":"Remove From Queue"},"e417":{"name":"Remove Red Eye"},"e8fe":{"name":"Reorder"},"e040":{"name":"Repeat"},"e041":{"name":"Repeat One"},"e042":{"name":"Replay"},"e059":{"name":"Replay 10"},"e05a":{"name":"Replay 30"},"e05b":{"name":"Replay 5"},"e15e":{"name":"Reply"},"e15f":{"name":"Reply All"},"e160":{"name":"Report"},"e8b2":{"name":"Report Problem"},"e561":{"name":"Restaurant Menu"},"e8b3":{"name":"Restore"},"e0d1":{"name":"Ring Volume"},"e8b4":{"name":"Room"},"eb49":{"name":"Room Service"},"e418":{"name":"Rotate 90 Degrees Ccw"},"e419":{"name":"Rotate Left"},"e41a":{"name":"Rotate Right"},"e920":{"name":"Rounded Corner"},"e328":{"name":"Router"},"e921":{"name":"Rowing"},"e642":{"name":"Rv Hookup"},"e562":{"name":"Satellite"},"e161":{"name":"Save"},"e329":{"name":"Scanner"},"e8b5":{"name":"Schedule"},"e80c":{"name":"School"},"e1be":{"name":"Screen Lock Landscape"},"e1bf":{"name":"Screen Lock Portrait"},"e1c0":{"name":"Screen Lock Rotation"},"e1c1":{"name":"Screen Rotation"},"e0e2":{"name":"Screen Share"},"e623":{"name":"Sd Card"},"e1c2":{"name":"Sd Storage"},"e8b6":{"name":"Search"},"e32a":{"name":"Security"},"e162":{"name":"Select All"},"e163":{"name":"Send"},"e8b8":{"name":"Settings"},"e8b9":{"name":"Settings Applications"},"e8ba":{"name":"Settings Backup Restore"},"e8bb":{"name":"Settings Bluetooth"},"e8bd":{"name":"Settings Brightness"},"e8bc":{"name":"Settings Cell"},"e8be":{"name":"Settings Ethernet"},"e8bf":{"name":"Settings Input Antenna"},"e8c0":{"name":"Settings Input Component"},"e8c1":{"name":"Settings Input Composite"},"e8c2":{"name":"Settings Input Hdmi"},"e8c3":{"name":"Settings Input Svideo"},"e8c4":{"name":"Settings Overscan"},"e8c5":{"name":"Settings Phone"},"e8c6":{"name":"Settings Power"},"e8c7":{"name":"Settings Remote"},"e1c3":{"name":"Settings System Daydream"},"e8c8":{"name":"Settings Voice"},"e80d":{"name":"Share"},"e8c9":{"name":"Shop"},"e8ca":{"name":"Shop Two"},"e8cb":{"name":"Shopping Basket"},"e8cc":{"name":"Shopping Cart"},"e261":{"name":"Short Text"},"e043":{"name":"Shuffle"},"e1c8":{"name":"Signal Cellular 4 Bar"},"e1cd":{"name":"Signal Cellular Connected No Internet 4 Bar"},"e1ce":{"name":"Signal Cellular No Sim"},"e1cf":{"name":"Signal Cellular Null"},"e1d0":{"name":"Signal Cellular Off"},"e1d8":{"name":"Signal Wifi 4 Bar"},"e1d9":{"name":"Signal Wifi 4 Bar Lock"},"e1da":{"name":"Signal Wifi Off"},"e32b":{"name":"Sim Card"},"e624":{"name":"Sim Card Alert"},"e044":{"name":"Skip Next"},"e045":{"name":"Skip Previous"},"e41b":{"name":"Slideshow"},"e068":{"name":"Slow Motion Video"},"e32c":{"name":"Smartphone"},"eb4a":{"name":"Smoke Free"},"eb4b":{"name":"Smoking Rooms"},"e625":{"name":"Sms"},"e626":{"name":"Sms Failed"},"e046":{"name":"Snooze"},"e164":{"name":"Sort"},"e053":{"name":"Sort By Alpha"},"eb4c":{"name":"Spa"},"e256":{"name":"Space Bar"},"e32d":{"name":"Speaker"},"e32e":{"name":"Speaker Group"},"e8cd":{"name":"Speaker Notes"},"e0d2":{"name":"Speaker Phone"},"e8ce":{"name":"Spellcheck"},"e838":{"name":"Star"},"e83a":{"name":"Star Border"},"e839":{"name":"Star Half"},"e8d0":{"name":"Stars"},"e0d3":{"name":"Stay Current Landscape"},"e0d4":{"name":"Stay Current Portrait"},"e0d5":{"name":"Stay Primary Landscape"},"e0d6":{"name":"Stay Primary Portrait"},"e047":{"name":"Stop"},"e0e3":{"name":"Stop Screen Share"},"e1db":{"name":"Storage"},"e8d1":{"name":"Store"},"e563":{"name":"Store Mall Directory"},"e41c":{"name":"Straighten"},"e257":{"name":"Strikethrough S"},"e41d":{"name":"Style"},"e5d9":{"name":"Subdirectory Arrow Left"},"e5da":{"name":"Subdirectory Arrow Right"},"e8d2":{"name":"Subject"},"e064":{"name":"Subscriptions"},"e048":{"name":"Subtitles"},"e8d3":{"name":"Supervisor Account"},"e049":{"name":"Surround Sound"},"e0d7":{"name":"Swap Calls"},"e8d4":{"name":"Swap Horiz"},"e8d5":{"name":"Swap Vert"},"e8d6":{"name":"Swap Vertical Circle"},"e41e":{"name":"Switch Camera"},"e41f":{"name":"Switch Video"},"e627":{"name":"Sync"},"e628":{"name":"Sync Disabled"},"e629":{"name":"Sync Problem"},"e62a":{"name":"System Update"},"e8d7":{"name":"System Update Alt"},"e8d8":{"name":"Tab"},"e8d9":{"name":"Tab Unselected"},"e32f":{"name":"Tablet"},"e330":{"name":"Tablet Android"},"e331":{"name":"Tablet Mac"},"e420":{"name":"Tag Faces"},"e62b":{"name":"Tap And Play"},"e564":{"name":"Terrain"},"e262":{"name":"Text Fields"},"e165":{"name":"Text Format"},"e0d8":{"name":"Textsms"},"e421":{"name":"Texture"},"e8da":{"name":"Theaters"},"e8db":{"name":"Thumb Down"},"e8dc":{"name":"Thumb Up"},"e8dd":{"name":"Thumbs Up Down"},"e62c":{"name":"Time To Leave"},"e422":{"name":"Timelapse"},"e922":{"name":"Timeline"},"e425":{"name":"Timer"},"e423":{"name":"Timer 10"},"e424":{"name":"Timer 3"},"e426":{"name":"Timer Off"},"e8de":{"name":"Toc"},"e8df":{"name":"Today"},"e8e0":{"name":"Toll"},"e427":{"name":"Tonality"},"e913":{"name":"Touch App"},"e332":{"name":"Toys"},"e8e1":{"name":"Track Changes"},"e565":{"name":"Traffic"},"e428":{"name":"Transform"},"e8e2":{"name":"Translate"},"e8e3":{"name":"Trending Down"},"e8e4":{"name":"Trending Flat"},"e8e5":{"name":"Trending Up"},"e429":{"name":"Tune"},"e8e6":{"name":"Turned In"},"e8e7":{"name":"Turned In Not"},"e333":{"name":"Tv"},"e169":{"name":"Unarchive"},"e166":{"name":"Undo"},"e5d6":{"name":"Unfold Less"},"e5d7":{"name":"Unfold More"},"e923":{"name":"Update"},"e1e0":{"name":"Usb"},"e8e8":{"name":"Verified User"},"e258":{"name":"Vertical Align Bottom"},"e259":{"name":"Vertical Align Center"},"e25a":{"name":"Vertical Align Top"},"e62d":{"name":"Vibration"},"e04a":{"name":"Video Library"},"e04b":{"name":"Videocam"},"e04c":{"name":"Videocam Off"},"e338":{"name":"Videogame Asset"},"e8e9":{"name":"View Agenda"},"e8ea":{"name":"View Array"},"e8eb":{"name":"View Carousel"},"e8ec":{"name":"View Column"},"e42a":{"name":"View Comfy"},"e42b":{"name":"View Compact"},"e8ed":{"name":"View Day"},"e8ee":{"name":"View Headline"},"e8ef":{"name":"View List"},"e8f0":{"name":"View Module"},"e8f1":{"name":"View Quilt"},"e8f2":{"name":"View Stream"},"e8f3":{"name":"View Week"},"e435":{"name":"Vignette"},"e8f4":{"name":"Visibility"},"e8f5":{"name":"Visibility Off"},"e62e":{"name":"Voice Chat"},"e0d9":{"name":"Voicemail"},"e04d":{"name":"Volume Down"},"e04e":{"name":"Volume Mute"},"e04f":{"name":"Volume Off"},"e050":{"name":"Volume Up"},"e0da":{"name":"Vpn Key"},"e62f":{"name":"Vpn Lock"},"e1bc":{"name":"Wallpaper"},"e002":{"name":"Warning"},"e334":{"name":"Watch"},"e924":{"name":"Watch Later"},"e42c":{"name":"Wb Auto"},"e42d":{"name":"Wb Cloudy"},"e42e":{"name":"Wb Incandescent"},"e436":{"name":"Wb Iridescent"},"e430":{"name":"Wb Sunny"},"e63d":{"name":"Wc"},"e051":{"name":"Web"},"e069":{"name":"Web Asset"},"e16b":{"name":"Weekend"},"e80e":{"name":"Whatshot"},"e1bd":{"name":"Widgets"},"e63e":{"name":"Wifi"},"e1e1":{"name":"Wifi Lock"},"e1e2":{"name":"Wifi Tethering"},"e8f9":{"name":"Work"},"e25b":{"name":"Wrap Text"},"e8fa":{"name":"Youtube Searched For"},"e8ff":{"name":"Zoom In"},"e900":{"name":"Zoom Out"},"e56b":{"name":"Zoom Out Map"}}} \ No newline at end of file diff --git a/font/MaterialIcons-Regular.ttf b/font/MaterialIcons-Regular.ttf new file mode 100644 index 00000000..683dcd05 Binary files /dev/null and b/font/MaterialIcons-Regular.ttf differ diff --git a/font/MaterialIcons-Regular.woff b/font/MaterialIcons-Regular.woff new file mode 100644 index 00000000..ddd6be3e Binary files /dev/null and b/font/MaterialIcons-Regular.woff differ diff --git a/font/MaterialIcons-Regular.woff2 b/font/MaterialIcons-Regular.woff2 new file mode 100644 index 00000000..974fea7c Binary files /dev/null and b/font/MaterialIcons-Regular.woff2 differ diff --git a/font/README.md b/font/README.md new file mode 100644 index 00000000..ce4141ec --- /dev/null +++ b/font/README.md @@ -0,0 +1,9 @@ +The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts: + +```html + +``` + +Read more in our full usage guide: +http://google.github.io/material-design-icons/#icon-font-for-the-web diff --git a/font/codepoints b/font/codepoints new file mode 100644 index 00000000..d5de0264 --- /dev/null +++ b/font/codepoints @@ -0,0 +1,891 @@ +3d_rotation e84d +ac_unit eb3b +access_alarm e190 +access_alarms e191 +access_time e192 +accessibility e84e +accessible e914 +account_balance e84f +account_balance_wallet e850 +account_box e851 +account_circle e853 +adb e60e +add e145 +add_a_photo e439 +add_alarm e193 +add_alert e003 +add_box e146 +add_circle e147 +add_circle_outline e148 +add_location e567 +add_shopping_cart e854 +add_to_photos e39d +add_to_queue e05c +adjust e39e +airline_seat_flat e630 +airline_seat_flat_angled e631 +airline_seat_individual_suite e632 +airline_seat_legroom_extra e633 +airline_seat_legroom_normal e634 +airline_seat_legroom_reduced e635 +airline_seat_recline_extra e636 +airline_seat_recline_normal e637 +airplanemode_active e195 +airplanemode_inactive e194 +airplay e055 +airport_shuttle eb3c +alarm e855 +alarm_add e856 +alarm_off e857 +alarm_on e858 +album e019 +all_inclusive eb3d +all_out e90b +android e859 +announcement e85a +apps e5c3 +archive e149 +arrow_back e5c4 +arrow_downward e5db +arrow_drop_down e5c5 +arrow_drop_down_circle e5c6 +arrow_drop_up e5c7 +arrow_forward e5c8 +arrow_upward e5d8 +art_track e060 +aspect_ratio e85b +assessment e85c +assignment e85d +assignment_ind e85e +assignment_late e85f +assignment_return e860 +assignment_returned e861 +assignment_turned_in e862 +assistant e39f +assistant_photo e3a0 +attach_file e226 +attach_money e227 +attachment e2bc +audiotrack e3a1 +autorenew e863 +av_timer e01b +backspace e14a +backup e864 +battery_alert e19c +battery_charging_full e1a3 +battery_full e1a4 +battery_std e1a5 +battery_unknown e1a6 +beach_access eb3e +beenhere e52d +block e14b +bluetooth e1a7 +bluetooth_audio e60f +bluetooth_connected e1a8 +bluetooth_disabled e1a9 +bluetooth_searching e1aa +blur_circular e3a2 +blur_linear e3a3 +blur_off e3a4 +blur_on e3a5 +book e865 +bookmark e866 +bookmark_border e867 +border_all e228 +border_bottom e229 +border_clear e22a +border_color e22b +border_horizontal e22c +border_inner e22d +border_left e22e +border_outer e22f +border_right e230 +border_style e231 +border_top e232 +border_vertical e233 +brightness_1 e3a6 +brightness_2 e3a7 +brightness_3 e3a8 +brightness_4 e3a9 +brightness_5 e3aa +brightness_6 e3ab +brightness_7 e3ac +brightness_auto e1ab +brightness_high e1ac +brightness_low e1ad +brightness_medium e1ae +broken_image e3ad +brush e3ae +bug_report e868 +build e869 +business e0af +business_center eb3f +cached e86a +cake e7e9 +call e0b0 +call_end e0b1 +call_made e0b2 +call_merge e0b3 +call_missed e0b4 +call_missed_outgoing e0e4 +call_received e0b5 +call_split e0b6 +camera e3af +camera_alt e3b0 +camera_enhance e8fc +camera_front e3b1 +camera_rear e3b2 +camera_roll e3b3 +cancel e5c9 +card_giftcard e8f6 +card_membership e8f7 +card_travel e8f8 +casino eb40 +cast e307 +cast_connected e308 +center_focus_strong e3b4 +center_focus_weak e3b5 +change_history e86b +chat e0b7 +chat_bubble e0ca +chat_bubble_outline e0cb +check e5ca +check_box e834 +check_box_outline_blank e835 +check_circle e86c +chevron_left e5cb +chevron_right e5cc +child_care eb41 +child_friendly eb42 +chrome_reader_mode e86d +class e86e +clear e14c +clear_all e0b8 +close e5cd +closed_caption e01c +cloud e2bd +cloud_circle e2be +cloud_done e2bf +cloud_download e2c0 +cloud_off e2c1 +cloud_queue e2c2 +cloud_upload e2c3 +code e86f +collections e3b6 +collections_bookmark e431 +color_lens e3b7 +colorize e3b8 +comment e0b9 +compare e3b9 +compare_arrows e915 +computer e30a +confirmation_number e638 +contact_mail e0d0 +contact_phone e0cf +contacts e0ba +content_copy e14d +content_cut e14e +content_paste e14f +control_point e3ba +control_point_duplicate e3bb +copyright e90c +create e150 +create_new_folder e2cc +credit_card e870 +crop e3be +crop_16_9 e3bc +crop_3_2 e3bd +crop_5_4 e3bf +crop_7_5 e3c0 +crop_din e3c1 +crop_free e3c2 +crop_landscape e3c3 +crop_original e3c4 +crop_portrait e3c5 +crop_rotate e437 +crop_square e3c6 +dashboard e871 +data_usage e1af +date_range e916 +dehaze e3c7 +delete e872 +description e873 +desktop_mac e30b +desktop_windows e30c +details e3c8 +developer_board e30d +developer_mode e1b0 +device_hub e335 +devices e1b1 +devices_other e337 +dialer_sip e0bb +dialpad e0bc +directions e52e +directions_bike e52f +directions_boat e532 +directions_bus e530 +directions_car e531 +directions_railway e534 +directions_run e566 +directions_subway e533 +directions_transit e535 +directions_walk e536 +disc_full e610 +dns e875 +do_not_disturb e612 +do_not_disturb_alt e611 +dock e30e +domain e7ee +done e876 +done_all e877 +donut_large e917 +donut_small e918 +drafts e151 +drag_handle e25d +drive_eta e613 +dvr e1b2 +edit e3c9 +edit_location e568 +eject e8fb +email e0be +enhanced_encryption e63f +equalizer e01d +error e000 +error_outline e001 +event e878 +event_available e614 +event_busy e615 +event_note e616 +event_seat e903 +exit_to_app e879 +expand_less e5ce +expand_more e5cf +explicit e01e +explore e87a +exposure e3ca +exposure_neg_1 e3cb +exposure_neg_2 e3cc +exposure_plus_1 e3cd +exposure_plus_2 e3ce +exposure_zero e3cf +extension e87b +face e87c +fast_forward e01f +fast_rewind e020 +favorite e87d +favorite_border e87e +feedback e87f +fiber_dvr e05d +fiber_manual_record e061 +fiber_new e05e +fiber_pin e06a +fiber_smart_record e062 +file_download e2c4 +file_upload e2c6 +filter e3d3 +filter_1 e3d0 +filter_2 e3d1 +filter_3 e3d2 +filter_4 e3d4 +filter_5 e3d5 +filter_6 e3d6 +filter_7 e3d7 +filter_8 e3d8 +filter_9 e3d9 +filter_9_plus e3da +filter_b_and_w e3db +filter_center_focus e3dc +filter_drama e3dd +filter_frames e3de +filter_hdr e3df +filter_list e152 +filter_none e3e0 +filter_tilt_shift e3e2 +filter_vintage e3e3 +find_in_page e880 +find_replace e881 +fingerprint e90d +fitness_center eb43 +flag e153 +flare e3e4 +flash_auto e3e5 +flash_off e3e6 +flash_on e3e7 +flight e539 +flight_land e904 +flight_takeoff e905 +flip e3e8 +flip_to_back e882 +flip_to_front e883 +folder e2c7 +folder_open e2c8 +folder_shared e2c9 +folder_special e617 +font_download e167 +format_align_center e234 +format_align_justify e235 +format_align_left e236 +format_align_right e237 +format_bold e238 +format_clear e239 +format_color_fill e23a +format_color_reset e23b +format_color_text e23c +format_indent_decrease e23d +format_indent_increase e23e +format_italic e23f +format_line_spacing e240 +format_list_bulleted e241 +format_list_numbered e242 +format_paint e243 +format_quote e244 +format_shapes e25e +format_size e245 +format_strikethrough e246 +format_textdirection_l_to_r e247 +format_textdirection_r_to_l e248 +format_underlined e249 +forum e0bf +forward e154 +forward_10 e056 +forward_30 e057 +forward_5 e058 +free_breakfast eb44 +fullscreen e5d0 +fullscreen_exit e5d1 +functions e24a +gamepad e30f +games e021 +gavel e90e +gesture e155 +get_app e884 +gif e908 +golf_course eb45 +gps_fixed e1b3 +gps_not_fixed e1b4 +gps_off e1b5 +grade e885 +gradient e3e9 +grain e3ea +graphic_eq e1b8 +grid_off e3eb +grid_on e3ec +group e7ef +group_add e7f0 +group_work e886 +hd e052 +hdr_off e3ed +hdr_on e3ee +hdr_strong e3f1 +hdr_weak e3f2 +headset e310 +headset_mic e311 +healing e3f3 +hearing e023 +help e887 +help_outline e8fd +high_quality e024 +highlight e25f +highlight_off e888 +history e889 +home e88a +hot_tub eb46 +hotel e53a +hourglass_empty e88b +hourglass_full e88c +http e902 +https e88d +image e3f4 +image_aspect_ratio e3f5 +import_contacts e0e0 +import_export e0c3 +important_devices e912 +inbox e156 +indeterminate_check_box e909 +info e88e +info_outline e88f +input e890 +insert_chart e24b +insert_comment e24c +insert_drive_file e24d +insert_emoticon e24e +insert_invitation e24f +insert_link e250 +insert_photo e251 +invert_colors e891 +invert_colors_off e0c4 +iso e3f6 +keyboard e312 +keyboard_arrow_down e313 +keyboard_arrow_left e314 +keyboard_arrow_right e315 +keyboard_arrow_up e316 +keyboard_backspace e317 +keyboard_capslock e318 +keyboard_hide e31a +keyboard_return e31b +keyboard_tab e31c +keyboard_voice e31d +kitchen eb47 +label e892 +label_outline e893 +landscape e3f7 +language e894 +laptop e31e +laptop_chromebook e31f +laptop_mac e320 +laptop_windows e321 +launch e895 +layers e53b +layers_clear e53c +leak_add e3f8 +leak_remove e3f9 +lens e3fa +library_add e02e +library_books e02f +library_music e030 +lightbulb_outline e90f +line_style e919 +line_weight e91a +linear_scale e260 +link e157 +linked_camera e438 +list e896 +live_help e0c6 +live_tv e639 +local_activity e53f +local_airport e53d +local_atm e53e +local_bar e540 +local_cafe e541 +local_car_wash e542 +local_convenience_store e543 +local_dining e556 +local_drink e544 +local_florist e545 +local_gas_station e546 +local_grocery_store e547 +local_hospital e548 +local_hotel e549 +local_laundry_service e54a +local_library e54b +local_mall e54c +local_movies e54d +local_offer e54e +local_parking e54f +local_pharmacy e550 +local_phone e551 +local_pizza e552 +local_play e553 +local_post_office e554 +local_printshop e555 +local_see e557 +local_shipping e558 +local_taxi e559 +location_city e7f1 +location_disabled e1b6 +location_off e0c7 +location_on e0c8 +location_searching e1b7 +lock e897 +lock_open e898 +lock_outline e899 +looks e3fc +looks_3 e3fb +looks_4 e3fd +looks_5 e3fe +looks_6 e3ff +looks_one e400 +looks_two e401 +loop e028 +loupe e402 +loyalty e89a +mail e158 +mail_outline e0e1 +map e55b +markunread e159 +markunread_mailbox e89b +memory e322 +menu e5d2 +merge_type e252 +message e0c9 +mic e029 +mic_none e02a +mic_off e02b +mms e618 +mode_comment e253 +mode_edit e254 +money_off e25c +monochrome_photos e403 +mood e7f2 +mood_bad e7f3 +more e619 +more_horiz e5d3 +more_vert e5d4 +motorcycle e91b +mouse e323 +move_to_inbox e168 +movie e02c +movie_creation e404 +movie_filter e43a +music_note e405 +music_video e063 +my_location e55c +nature e406 +nature_people e407 +navigate_before e408 +navigate_next e409 +navigation e55d +near_me e569 +network_cell e1b9 +network_check e640 +network_locked e61a +network_wifi e1ba +new_releases e031 +next_week e16a +nfc e1bb +no_encryption e641 +no_sim e0cc +not_interested e033 +note_add e89c +notifications e7f4 +notifications_active e7f7 +notifications_none e7f5 +notifications_off e7f6 +notifications_paused e7f8 +offline_pin e90a +ondemand_video e63a +opacity e91c +open_in_browser e89d +open_in_new e89e +open_with e89f +pages e7f9 +pageview e8a0 +palette e40a +pan_tool e925 +panorama e40b +panorama_fish_eye e40c +panorama_horizontal e40d +panorama_vertical e40e +panorama_wide_angle e40f +party_mode e7fa +pause e034 +pause_circle_filled e035 +pause_circle_outline e036 +payment e8a1 +people e7fb +people_outline e7fc +perm_camera_mic e8a2 +perm_contact_calendar e8a3 +perm_data_setting e8a4 +perm_device_information e8a5 +perm_identity e8a6 +perm_media e8a7 +perm_phone_msg e8a8 +perm_scan_wifi e8a9 +person e7fd +person_add e7fe +person_outline e7ff +person_pin e55a +person_pin_circle e56a +personal_video e63b +pets e91d +phone e0cd +phone_android e324 +phone_bluetooth_speaker e61b +phone_forwarded e61c +phone_in_talk e61d +phone_iphone e325 +phone_locked e61e +phone_missed e61f +phone_paused e620 +phonelink e326 +phonelink_erase e0db +phonelink_lock e0dc +phonelink_off e327 +phonelink_ring e0dd +phonelink_setup e0de +photo e410 +photo_album e411 +photo_camera e412 +photo_filter e43b +photo_library e413 +photo_size_select_actual e432 +photo_size_select_large e433 +photo_size_select_small e434 +picture_as_pdf e415 +picture_in_picture e8aa +picture_in_picture_alt e911 +pin_drop e55e +place e55f +play_arrow e037 +play_circle_filled e038 +play_circle_outline e039 +play_for_work e906 +playlist_add e03b +playlist_add_check e065 +playlist_play e05f +plus_one e800 +poll e801 +polymer e8ab +pool eb48 +portable_wifi_off e0ce +portrait e416 +power e63c +power_input e336 +power_settings_new e8ac +pregnant_woman e91e +present_to_all e0df +print e8ad +public e80b +publish e255 +query_builder e8ae +question_answer e8af +queue e03c +queue_music e03d +queue_play_next e066 +radio e03e +radio_button_checked e837 +radio_button_unchecked e836 +rate_review e560 +receipt e8b0 +recent_actors e03f +record_voice_over e91f +redeem e8b1 +redo e15a +refresh e5d5 +remove e15b +remove_circle e15c +remove_circle_outline e15d +remove_from_queue e067 +remove_red_eye e417 +reorder e8fe +repeat e040 +repeat_one e041 +replay e042 +replay_10 e059 +replay_30 e05a +replay_5 e05b +reply e15e +reply_all e15f +report e160 +report_problem e8b2 +restaurant_menu e561 +restore e8b3 +ring_volume e0d1 +room e8b4 +room_service eb49 +rotate_90_degrees_ccw e418 +rotate_left e419 +rotate_right e41a +rounded_corner e920 +router e328 +rowing e921 +rv_hookup e642 +satellite e562 +save e161 +scanner e329 +schedule e8b5 +school e80c +screen_lock_landscape e1be +screen_lock_portrait e1bf +screen_lock_rotation e1c0 +screen_rotation e1c1 +screen_share e0e2 +sd_card e623 +sd_storage e1c2 +search e8b6 +security e32a +select_all e162 +send e163 +settings e8b8 +settings_applications e8b9 +settings_backup_restore e8ba +settings_bluetooth e8bb +settings_brightness e8bd +settings_cell e8bc +settings_ethernet e8be +settings_input_antenna e8bf +settings_input_component e8c0 +settings_input_composite e8c1 +settings_input_hdmi e8c2 +settings_input_svideo e8c3 +settings_overscan e8c4 +settings_phone e8c5 +settings_power e8c6 +settings_remote e8c7 +settings_system_daydream e1c3 +settings_voice e8c8 +share e80d +shop e8c9 +shop_two e8ca +shopping_basket e8cb +shopping_cart e8cc +short_text e261 +shuffle e043 +signal_cellular_4_bar e1c8 +signal_cellular_connected_no_internet_4_bar e1cd +signal_cellular_no_sim e1ce +signal_cellular_null e1cf +signal_cellular_off e1d0 +signal_wifi_4_bar e1d8 +signal_wifi_4_bar_lock e1d9 +signal_wifi_off e1da +sim_card e32b +sim_card_alert e624 +skip_next e044 +skip_previous e045 +slideshow e41b +slow_motion_video e068 +smartphone e32c +smoke_free eb4a +smoking_rooms eb4b +sms e625 +sms_failed e626 +snooze e046 +sort e164 +sort_by_alpha e053 +spa eb4c +space_bar e256 +speaker e32d +speaker_group e32e +speaker_notes e8cd +speaker_phone e0d2 +spellcheck e8ce +star e838 +star_border e83a +star_half e839 +stars e8d0 +stay_current_landscape e0d3 +stay_current_portrait e0d4 +stay_primary_landscape e0d5 +stay_primary_portrait e0d6 +stop e047 +stop_screen_share e0e3 +storage e1db +store e8d1 +store_mall_directory e563 +straighten e41c +strikethrough_s e257 +style e41d +subdirectory_arrow_left e5d9 +subdirectory_arrow_right e5da +subject e8d2 +subscriptions e064 +subtitles e048 +supervisor_account e8d3 +surround_sound e049 +swap_calls e0d7 +swap_horiz e8d4 +swap_vert e8d5 +swap_vertical_circle e8d6 +switch_camera e41e +switch_video e41f +sync e627 +sync_disabled e628 +sync_problem e629 +system_update e62a +system_update_alt e8d7 +tab e8d8 +tab_unselected e8d9 +tablet e32f +tablet_android e330 +tablet_mac e331 +tag_faces e420 +tap_and_play e62b +terrain e564 +text_fields e262 +text_format e165 +textsms e0d8 +texture e421 +theaters e8da +thumb_down e8db +thumb_up e8dc +thumbs_up_down e8dd +time_to_leave e62c +timelapse e422 +timeline e922 +timer e425 +timer_10 e423 +timer_3 e424 +timer_off e426 +toc e8de +today e8df +toll e8e0 +tonality e427 +touch_app e913 +toys e332 +track_changes e8e1 +traffic e565 +transform e428 +translate e8e2 +trending_down e8e3 +trending_flat e8e4 +trending_up e8e5 +tune e429 +turned_in e8e6 +turned_in_not e8e7 +tv e333 +unarchive e169 +undo e166 +unfold_less e5d6 +unfold_more e5d7 +update e923 +usb e1e0 +verified_user e8e8 +vertical_align_bottom e258 +vertical_align_center e259 +vertical_align_top e25a +vibration e62d +video_library e04a +videocam e04b +videocam_off e04c +videogame_asset e338 +view_agenda e8e9 +view_array e8ea +view_carousel e8eb +view_column e8ec +view_comfy e42a +view_compact e42b +view_day e8ed +view_headline e8ee +view_list e8ef +view_module e8f0 +view_quilt e8f1 +view_stream e8f2 +view_week e8f3 +vignette e435 +visibility e8f4 +visibility_off e8f5 +voice_chat e62e +voicemail e0d9 +volume_down e04d +volume_mute e04e +volume_off e04f +volume_up e050 +vpn_key e0da +vpn_lock e62f +wallpaper e1bc +warning e002 +watch e334 +watch_later e924 +wb_auto e42c +wb_cloudy e42d +wb_incandescent e42e +wb_iridescent e436 +wb_sunny e430 +wc e63d +web e051 +web_asset e069 +weekend e16b +whatshot e80e +widgets e1bd +wifi e63e +wifi_lock e1e1 +wifi_tethering e1e2 +work e8f9 +wrap_text e25b +youtube_searched_for e8fa +zoom_in e8ff +zoom_out e900 +zoom_out_map e56b diff --git a/font/font.scss b/font/font.scss new file mode 100644 index 00000000..f5eb8525 --- /dev/null +++ b/font/font.scss @@ -0,0 +1,53 @@ + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(./font/MaterialIcons-Regular.eot); /* For IE6-8 */ + src: local('./Material Icons'), + local('./MaterialIcons-Regular'), + url(./font/MaterialIcons-Regular.woff2) format('woff2'), + url(./font/MaterialIcons-Regular.woff) format('woff'), + url(./font/MaterialIcons-Regular.ttf) format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + width: 1em; + height: 1em; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} + +// Rules for sizing the icon. +.material-icons.md-18 { font-size: 18px; } +.material-icons.md-24 { font-size: 24px; } +.material-icons.md-36 { font-size: 36px; } +.material-icons.md-48 { font-size: 48px; } + +// Rules for using icons as black on a light background. +.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); } +.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); } + +// Rules for using icons as white on a dark background. +.material-icons.md-light { color: rgba(255, 255, 255, 1); } +.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } diff --git a/ng2-material/all.scss b/ng2-material/all.scss new file mode 100644 index 00000000..2fcb7829 --- /dev/null +++ b/ng2-material/all.scss @@ -0,0 +1,25 @@ +@import "core/style/variables"; +@import "core/style/mixins"; +@import "core/style/structure"; +@import "core/style/typography"; +@import "core/style/layout"; +@import "core/style/default-theme"; + + +@import "components/button/button"; +@import "components/card/card"; +@import "components/content/content"; +@import "components/checkbox/checkbox"; +@import "components/dialog/dialog"; +@import "components/divider/divider"; +@import "components/grid_list/grid_list"; +@import "components/icon/icon"; +@import "components/input/input"; +@import "components/list/list"; +@import "components/progress_circular/progress_circular"; +@import "components/progress_linear/progress_linear"; +@import "components/radio/radio_button"; +@import "components/radio/radio_group"; +@import "components/sidenav/sidenav"; +@import "components/switcher/switch"; +@import "components/toolbar/toolbar"; diff --git a/ng2-material/all.ts b/ng2-material/all.ts new file mode 100644 index 00000000..e4c342d7 --- /dev/null +++ b/ng2-material/all.ts @@ -0,0 +1,51 @@ +import {CONST_EXPR, Type} from 'angular2/src/facade/lang'; + +import {MdAnchor, MdButton} from './components/button/button'; +import {MdCheckbox} from './components/checkbox/checkbox'; +import {MdContent} from './components/content/content'; +import {MdDivider} from './components/divider/divider'; +import {MdGridList, MdGridTile} from './components/grid_list/grid_list'; +import {MdIcon} from './components/icon/icon'; +import {MdInput, MdInputContainer} from './components/input/input'; +import {MdList, MdListItem} from './components/list/list'; +import {MdProgressLinear} from './components/progress_linear/progress_linear'; +import {MdProgressCircular} from './components/progress_circular/progress_circular'; +import {MdRadioButton, MdRadioGroup} from './components/radio/radio_button'; +import {MdSwitch} from './components/switcher/switch'; +import {MdToolbar} from './components/toolbar/toolbar'; + +/** + * Collection of Material Design component directives. + */ +export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([ + MdAnchor, MdButton, + MdCheckbox, + MdContent, + MdDivider, + MdGridList, MdGridTile, + MdIcon, + MdInput, MdInputContainer, + MdList, MdListItem, + MdProgressLinear, + MdProgressCircular, + MdRadioButton, MdRadioGroup, + MdSwitch, + MdToolbar +]); + +// Re-export all components +export * from './components/button/button'; +export * from './components/checkbox/checkbox'; +export * from './components/content/content'; +export * from './components/dialog/dialog'; +export * from './components/divider/divider'; +export * from './components/grid_list/grid_list'; +export * from './components/icon/icon'; +export * from './components/input/input'; +export * from './components/list/list'; +export * from './components/progress_linear/progress_linear'; +export * from './components/progress_circular/progress_circular'; +export * from './components/radio/radio_button'; +export * from './components/radio/radio_dispatcher'; +export * from './components/switcher/switch'; +export * from './components/toolbar/toolbar'; diff --git a/ng2-material/components/button/button.html b/ng2-material/components/button/button.html new file mode 100644 index 00000000..4e3df0e2 --- /dev/null +++ b/ng2-material/components/button/button.html @@ -0,0 +1 @@ + diff --git a/ng2-material/components/button/button.scss b/ng2-material/components/button/button.scss new file mode 100644 index 00000000..b0a916a1 --- /dev/null +++ b/ng2-material/components/button/button.scss @@ -0,0 +1,262 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +// TODO(jelbourn): Move variables and mixins into a partial file. +// TODO(jelbourn): Measure perf benefits for translate3d and will-change. +// TODO(jelbourn): Figure out if anchor hover underline actually happens in any browser. + + +// Standard button sizing. +$md-button-padding: 0 rem(0.600) !default; +$md-button-min-width: rem(8.800) !default; +$md-button-margin: rem(0.600) rem(0.800) !default; +$md-button-line-height: rem(3.60) !default; +$md-button-border-radius: 3px !default; + +// FAB sizing. +$md-fab-border-radius: 50%; +$md-fab-size: rem(5.600) !default; +$md-fab-line-height: rem(5.600) !default; +$md-fab-padding: rem(1.60) !default; +$md-fab-mini-size: rem(4.00) !default; +$md-fab-mini-line-height: rem(4.00) !default; + +// Icon button sizing. +$md-icon-button-height: rem(4.000) !default; +$md-icon-button-width: rem(4.000) !default; +$md-icon-button-margin: rem(0.600) !default; +$md-icon-border-radius: $md-fab-border-radius; + +/** Mixin to create distinct classes for fab positions, e.g. ".md-fab-position-bottom-right". */ +@mixin md-fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) { + .md-fab-position-#{$spot} { + top: $top; + right: $right; + bottom: $bottom; + left: $left; + position: absolute; + } +} + +/** Mixin to set button size to fit an icon */ +@mixin md-button-icon { + margin: 0 $md-icon-button-margin; + height: $md-icon-button-height; + min-width: 0; + line-height: $icon-size; + padding: $baseline-grid; + width: $md-icon-button-width; + border-radius: $md-icon-border-radius; +} + +/** Styles for all disabled buttons. */ +@mixin md-button-disabled() { + color: md-color($md-foreground, disabled); + background-color: transparent; + cursor: default; +} + +/** Base styles for all buttons. */ +@mixin md-button-base() { + box-sizing: border-box; + position: relative; + + // Reset browser styles. + background: transparent; + text-align: center; + overflow: hidden; + cursor: pointer; + user-select: none; + outline: none; + border: none; + + // Make anchors render like buttons. + display: inline-block; + white-space: nowrap; + text-decoration: none; + vertical-align: middle; + + // Typography. + font-size: $md-body-font-size-base; + font-weight: 500; + text-transform: uppercase; + + // Sizing. + padding: $md-button-padding; + margin: $md-button-margin; + min-width: $md-button-min-width; + line-height: $md-button-line-height; + border-radius: $md-button-border-radius; + + // Animation. + transition: background $swift-ease-out-duration $swift-ease-out-timing-function, + box-shadow $swift-ease-out-duration $swift-ease-out-timing-function; + + // Hide the browser focus indicator, instead applying our own focus style on background-color. + &:focus { + outline: none; + } + + &:hover, &:focus { + // Remove anchor underline again for more specific modifiers. + text-decoration: none; + } + + // Use a CSS class for focus style because we only want to render the focus style when + // the focus originated from a keyboard event (see JS source for more details). + &.md-button-focus { + background: md-color($md-background, 500, 0.2); + } + + &.md-primary { + color: md-color($md-primary); + } + + &.md-accent { + color: md-color($md-accent); + } + + &.md-warn { + color: md-color($md-warn); + } + + &.md-icon { + padding: 0; + background: none; + } + + &.md-icon-button { + @include md-button-icon(); + } + + + // Use the [disabled] attribute instead of the :disabled pseudo-class because anchors + // cannot technically be :disabled. + &[disabled] { + @include md-button-disabled(); + } +} + +/** Base styles for raised buttons, including FABs. */ +@mixin md-raised-button() { + @include md-button-base(); + + // Force hardware acceleration. + transform: translate3d(0, 0, 0); + + box-shadow: $md-shadow-bottom-z-1; + + &:active { + box-shadow: $md-shadow-bottom-z-2; + } + + &[disabled] { + box-shadow: none; + } + + &.md-primary { + color: md-color($md-primary, default-contrast); + background-color: md-color($md-primary); + + &:hover, &.md-button-focus { + background-color: md-color($md-primary, 600); + } + } + + &.md-accent { + color: md-color($md-accent, default-contrast); + background-color: md-color($md-accent); + + &:hover, &.md-button-focus { + background-color: md-color($md-accent, A700); + } + } + + &.md-warn { + color: md-color($md-warn,default-contrast); //'{{warn-contrast}}'; + background-color: md-color($md-warn); //'{{warn-color}}'; + [md-icon] { + color: md-color($md-warn,default-contrast); //'{{warn-contrast}}'; + } + &:hover { + background-color: md-color($md-warn); //'{{warn-color}}'; + } + &.md-focused { + background-color: md-color($md-warn,700); //'{{warn-700}}'; + } + } + + &.md-primary, &.md-accent { + &[disabled] { + @include md-button-disabled(); + } + } +} + + +[md-button] { + @include md-button-base(); + &:hover { + background: md-color($md-background, 500, 0.2); + } +} + +[md-raised-button] { + @include md-raised-button(); + + color: md-color($md-background, default-contrast); + background-color: md-color($md-background, 50); +} + +[md-fab] { + @include md-raised-button(); + @include md-button-icon(); + + z-index: $z-index-fab; + + border-radius: $md-fab-border-radius; + min-width: 0; + width: $md-fab-size; + height: $md-fab-size; + line-height: $md-fab-line-height; + vertical-align: middle; + + background-color: md-color($md-accent); //'{{accent-color}}'; + color: md-color($md-accent,default-contrast); //'{{accent-contrast}}'; + [md-icon] { + color: md-color($md-accent,default-contrast); //'{{accent-contrast}}'; + } + &:not([disabled]) { + &:hover { + background-color: md-color($md-accent); //'{{accent-color}}'; + } + &.md-focused { + background-color: md-color($md-accent,A700); //'{{accent-A700}}'; + } + } + + + &.md-mini { + line-height: $md-fab-mini-line-height; + width: $md-fab-mini-size; + height: $md-fab-mini-size; + } +} + +// Styles for high contrast mode. +@media screen and (-ms-high-contrast: active) { + [md-raised], + [md-fab] { + border: 1px solid #fff; + } +} + +$md-fab-pos-offset: ($md-fab-size - $md-fab-padding) / 2; +@include md-fab-position(bottom-right, auto, $md-fab-pos-offset, $md-fab-pos-offset, auto); +@include md-fab-position(bottom-left, auto, auto, $md-fab-pos-offset, $md-fab-pos-offset); +@include md-fab-position(top-right, $md-fab-pos-offset, $md-fab-pos-offset, auto, auto); +@include md-fab-position(top-left, $md-fab-pos-offset, auto, auto, $md-fab-pos-offset); + diff --git a/ng2-material/components/button/button.ts b/ng2-material/components/button/button.ts new file mode 100644 index 00000000..aeffc660 --- /dev/null +++ b/ng2-material/components/button/button.ts @@ -0,0 +1,97 @@ +import {Component, View, ViewEncapsulation, OnChanges} from 'angular2/angular2'; + +import {TimerWrapper} from 'angular2/src/facade/async'; +import {isPresent} from 'angular2/src/facade/lang'; + + +// TODO(jelbourn): Ink ripples. +// TODO(jelbourn): Make the `isMouseDown` stuff done with one global listener. + +@Component({ + selector: '[md-button]:not(a), [md-fab]:not(a), [md-raised-button]:not(a)', + host: { + '(mousedown)': 'onMousedown()', + '(focus)': 'onFocus()', + '(blur)': 'onBlur()', + '[class.md-button-focus]': 'isKeyboardFocused', + }, +}) +@View({ + templateUrl: 'ng2-material/components/button/button.html', + styleUrls: ['ng2-material/components/button/button.css'], + encapsulation: ViewEncapsulation.None, +}) +export class MdButton { + /** Whether a mousedown has occured on this element in the last 100ms. */ + isMouseDown: boolean = false; + + /** Whether the button has focus from the keyboard (not the mouse). Used for class binding. */ + isKeyboardFocused: boolean = false; + + onMousedown() { + // We only *show* the focus style when focus has come to the button via the keyboard. + // The Material Design spec is silent on this topic, and without doing this, the + // button continues to look :active after clicking. + // @see http://marcysutton.com/button-focus-hell/ + this.isMouseDown = true; + TimerWrapper.setTimeout(() => {this.isMouseDown = false}, 100); + } + + onFocus() { + this.isKeyboardFocused = !this.isMouseDown; + } + + onBlur() { + this.isKeyboardFocused = false; + } +} + + +@Component({ + selector: 'a[md-button], a[md-raised-button], a[md-fab]', + inputs: ['disabled'], + host: { + '(click)': 'onClick($event)', + '(mousedown)': 'onMousedown()', + '(focus)': 'onFocus()', + '(blur)': 'onBlur()', + '[tabIndex]': 'tabIndex', + '[class.md-button-focus]': 'isKeyboardFocused', + '[attr.aria-disabled]': 'isAriaDisabled', + }, +}) +@View({ + templateUrl: 'ng2-material/components/button/button.html', + encapsulation: ViewEncapsulation.None +}) +export class MdAnchor extends MdButton implements OnChanges { + tabIndex: number; + disabled_: boolean; + + get disabled(): boolean { + return this.disabled_; + } + + set disabled(value) { + // The presence of *any* disabled value makes the component disabled, *except* for false. + this.disabled_ = isPresent(value) && this.disabled !== false; + } + + onClick(event) { + // A disabled anchor shouldn't navigate anywhere. + if (this.disabled) { + event.preventDefault(); + } + } + + /** Invoked when a change is detected. */ + ngOnChanges(_) { + // A disabled anchor should not be in the tab flow. + this.tabIndex = this.disabled ? -1 : 0; + } + + /** Gets the aria-disabled value for the component, which must be a string for Dart. */ + get isAriaDisabled(): string { + return this.disabled ? 'true' : 'false'; + } +} diff --git a/ng2-material/components/card/card.scss b/ng2-material/components/card/card.scss new file mode 100644 index 00000000..32c0b85e --- /dev/null +++ b/ng2-material/components/card/card.scss @@ -0,0 +1,249 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; +@import "../../core/style/default-theme"; + +$card-padding: 16px !default; +$card-box-shadow: $whiteframe-shadow-z1 !default; + +md-card { + box-sizing: border-box; + display: flex; + flex-direction: column; + margin: $baseline-grid; + + box-shadow: $card-box-shadow; + + md-card-header { + padding: $card-padding; + display: flex; + flex-direction: row; + + &:first-child { + md-card-avatar { + margin-right: 12px; + } + } + + &:last-child { + md-card-avatar { + margin-left: 12px; + } + } + + md-card-avatar { + width: 40px; + height: 40px; + + .md-user-avatar, + md-icon { + border-radius: 50%; + } + + md-icon { + padding: 8px; + } + + & + md-card-header-text { + max-height: 40px; + + .md-title { + font-size: 14px; + } + } + } + + md-card-header-text { + display: flex; + flex: 1; + flex-direction: column; + + .md-subhead { + font-size: 14px; + } + } + } + + > img, + > :not(md-card-content) img { + display: flex; + flex: 0 0 auto; + width: 100%; + height: auto; + } + + md-card-title { + padding: 3 * $card-padding / 2 $card-padding $card-padding; + display: flex; + flex: 1; + flex-direction: row; + + & + md-card-content { + padding-top: 0; + } + + md-card-title-text { + flex: 1; + flex-direction: column; + display: flex; + + .md-subhead { + padding-top: 0; + font-size: 14px; + } + + &:only-child { + .md-subhead { + padding-top: 3 * $card-padding / 4; + } + } + } + + md-card-title-media { + margin-top: - $card-padding / 2; + + .md-media-sm { + height: 80px; + width: 80px; + } + .md-media-md { + height: 112px; + width: 112px; + } + .md-media-lg { + height: 152px; + width: 152px; + } + } + } + + md-card-content { + display: block; + padding: $card-padding; + + & > p { + margin: 0; + } + + .md-media-xl { + height: 240px; + width: 240px; + } + } + + .md-actions, md-card-actions { + margin: $baseline-grid; + + &.layout-column { + .md-button { + &:not(.md-icon-button) { + margin: $baseline-grid / 4 0; + + &:first-of-type { + margin-top: 0; + } + + &:last-of-type { + margin-bottom: 0; + } + } + + &.md-icon-button { + margin-top: 3 * $baseline-grid / 4; + margin-bottom: 3 * $baseline-grid / 4; + } + } + } + + md-card-icon-actions { + flex: 1; + justify-content: flex-start; + display: flex; + flex-direction: row; + } + + &:not(.layout-column) .md-button { + &:not(.md-icon-button) { + margin: 0 $baseline-grid * .5; + + &:first-of-type { + margin-left: 0; + } + + &:last-of-type { + margin-right: 0; + } + } + + &.md-icon-button { + margin-left: 3 * $baseline-grid / 4; + margin-right: 3 * $baseline-grid / 4; + + &:first-of-type { + margin-left: 3 * $baseline-grid / 2; + } + + &:last-of-type { + margin-right: 3 * $baseline-grid / 2; + } + } + + & + md-card-icon-actions { + flex: 1; + justify-content: flex-end; + display: flex; + flex-direction: row; + } + } + } + + md-card-footer { + margin-top: auto; + padding: $card-padding; + } +} + +@media screen and (-ms-high-contrast: active) { + md-card { + border: 1px solid #fff; + } +} + +// +// Theme +// + +$card-border-radius: 2px !default; + +md-card.md-THEME_NAME-theme { + background-color: md-color($md-background, 500); //'{{background-color}}'; + border-radius: $card-border-radius; + + .md-card-image { + border-radius: $card-border-radius $card-border-radius 0 0; + } + + md-card-header { + md-card-avatar { + md-icon { + color: md-color($md-background); //'{{background-color}}'; + background-color: md-color($md-foreground, hint-text); //'{{foreground-3}}'; + } + } + + md-card-header-text { + .md-subhead { + color: md-color($md-foreground, text); //'{{foreground-2}}' + } + } + } + + md-card-title { + md-card-title-text { + &:not(:only-child) { + .md-subhead { + color: md-color($md-foreground, text); // '{{foreground-2}}'; + } + } + } + } +} diff --git a/ng2-material/components/card/card.ts b/ng2-material/components/card/card.ts new file mode 100644 index 00000000..a633de44 --- /dev/null +++ b/ng2-material/components/card/card.ts @@ -0,0 +1,109 @@ +import {Directive} from "angular2/core"; + +/** + * @name mdCard + * + * @description + * The `` directive is a container element used within `` containers. + * + * An image included as a direct descendant will fill the card's width, while the `` + * container will wrap text content and provide padding. An `` element can be + * optionally included to put content flush against the bottom edge of the card. + * + * Action buttons can be included in an `` element, similar to ``. + * You can then position buttons using layout attributes. + * + * Card is built with: + * * `` - Header for the card, holds avatar, text and squared image + * - `` - Card avatar + * - `md-user-avatar` - Class for user image + * - `` + * - `` - Contains elements for the card description + * - `md-title` - Class for the card title + * - `md-subhead` - Class for the card sub header + * * `` - Image for the card + * * `` - Card content title + * - `` + * - `md-headline` - Class for the card content title + * - `md-subhead` - Class for the card content sub header + * - `` - Squared image within the title + * - `md-media-sm` - Class for small image + * - `md-media-md` - Class for medium image + * - `md-media-lg` - Class for large image + * * `` - Card content + * - `md-media-xl` - Class for extra large image + * * `` - Card actions + * - `` - Icon actions + * + * Cards have constant width and variable heights; where the maximum height is limited to what can + * fit within a single view on a platform, but it can temporarily expand as needed. + * + * @usage + * ### Card with optional footer + * + * + * + * + * Card headline + * Card content + * + * + * Card footer + * + * + * + * + * ### Card with actions + * + * + * + * + * Card headline + * Card content + * + * + * Action 1 + * Action 2 + * + * + * + * + * ### Card with header, image, title actions and content + * + * + * + * + * + * + * + * Title + * Sub header + * + * + * + * + * + * Card headline + * Card subheader + * + * + * + * Action 1 + * Action 2 + * + * + * + * + * + * + * + * + * Card content + * + * + * + * + */ +@Directive({selector: 'md-card'}) +export class MdContent { +} diff --git a/ng2-material/components/checkbox/checkbox.html b/ng2-material/components/checkbox/checkbox.html new file mode 100644 index 00000000..86d69aba --- /dev/null +++ b/ng2-material/components/checkbox/checkbox.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/ng2-material/components/checkbox/checkbox.scss b/ng2-material/components/checkbox/checkbox.scss new file mode 100644 index 00000000..7764f5bf --- /dev/null +++ b/ng2-material/components/checkbox/checkbox.scss @@ -0,0 +1,176 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$checkbox-width: 18px !default; +$checkbox-height: $checkbox-width !default; + +md-checkbox { + box-sizing: border-box; + display: block; + margin: 15px; + white-space: nowrap; + cursor: pointer; + outline: none; + user-select: none; + + *, *:after { + box-sizing: border-box; + } + + &[aria-checked="true"] .md-checkbox-icon { + border: none; + } + + // Checkbox is disabled. + &[disabled] { + cursor: no-drop; + } + + // Checkbox is focused. + &:focus .md-checkbox-label:not(:empty) { + border-color: black; + } + + // Checkbox is checked. + &[aria-checked="true"] .md-checkbox-icon:after { + transform: rotate(45deg); + position: absolute; + left: 6px; + top: 2px; + display: table; + width: 6px; + height: 12px; + border: 2px solid; + border-top: 0; + border-left: 0; + content: ' '; + } +} + +.md-checkbox-container { + position: relative; + top: 4px; + display: inline-block; + width: $checkbox-width; + height: $checkbox-height; + + &:after { + content: ''; + position: absolute; + top: -10px; + right: -10px; + bottom: -10px; + left: -10px; + } + + .md-ripple-container { + position: absolute; + display: block; + width: auto; + height: auto; + left: -15px; + top: -15px; + right: -15px; + bottom: -15px; + } +} + +// Checkbox is not checked. +.md-checkbox-icon { + transition: 240ms; + position: absolute; + top: 0; + left: 0; + width: $checkbox-width; + height: $checkbox-height; + border: 2px solid; + border-radius: 2px; +} + +.md-checkbox-label { + border: 1px dotted transparent; + position: relative; + display: inline-block; + margin-left: 10px; + vertical-align: middle; + white-space: normal; + pointer-events: none; + user-select: text; +} + +// THEME +// TODO(jelbourn): ripple + +md-checkbox { + + .md-ripple { + color: md-color($md-accent, 600); + } + &[aria-checked="true"] .md-ripple { + color: md-color($md-background, 600); + } + + .md-checkbox-icon { + border-color: md-color($md-foreground, icon); + } + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-accent, 0.87); + } + + &[aria-checked="true"] .md-checkbox-icon:after { + border-color: md-color($md-background, 200); + } + + &:not([disabled]) { + &.md-primary { + .md-ripple { + color: md-color($md-primary, 600); + } + &[aria-checked="true"] .md-ripple { + color: md-color($md-background, 600); + } + + .md-checkbox-icon { + border-color: md-color($md-foreground, icon); + } + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-primary, 0.87); + } + + &[aria-checked="true"] .md-checkbox-icon:after { + border-color: md-color($md-background, 200); + } + } + + &.md-warn { + .md-ripple { + color: md-color($md-warn, 600); + } + + .md-checkbox-icon { + border-color: md-color($md-foreground, icon); + } + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-warn, 0.87); + } + + &[aria-checked="true"] .md-checkbox-icon:after { + border-color: md-color($md-background, 200); + } + } + } + + &[disabled] { + .md-checkbox-icon { + border-color: md-color($md-foreground, disabled); + } + + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-foreground, disabled); + } + } + +} diff --git a/ng2-material/components/checkbox/checkbox.ts b/ng2-material/components/checkbox/checkbox.ts new file mode 100644 index 00000000..bcbe5575 --- /dev/null +++ b/ng2-material/components/checkbox/checkbox.ts @@ -0,0 +1,67 @@ +import {Component, View, Attribute, ViewEncapsulation} from 'angular2/angular2'; +import {isPresent} from 'angular2/src/facade/lang'; +import {KeyCodes} from '../../core/key_codes'; +import {KeyboardEvent} from 'angular2/src/facade/browser'; +import {NumberWrapper} from 'angular2/src/facade/lang'; +import {Output, EventEmitter} from "angular2/core"; + +@Component({ + selector: 'md-checkbox', + inputs: ['checked', 'disabled'], + host: { + 'role': 'checkbox', + '[attr.aria-checked]': 'checked', + '[attr.aria-disabled]': 'disabled', + '[tabindex]': 'tabindex', + '(keydown)': 'onKeydown($event)', + } +}) +@View({ + templateUrl: 'ng2-material/components/checkbox/checkbox.html', + directives: [], + encapsulation: ViewEncapsulation.None +}) +export class MdCheckbox { + + @Output() ngOnChange:EventEmitter = new EventEmitter(); + + /** Whether this checkbox is checked. */ + checked: boolean; + + /** Whether this checkbox is disabled. */ + disabled_: boolean; + + /** Setter for tabindex */ + tabindex: number; + + constructor(@Attribute('tabindex') tabindex: string) { + this.checked = false; + this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0; + this.disabled_ = false; + } + + get disabled() { + return this.disabled_; + } + + set disabled(value) { + this.disabled_ = isPresent(value) && value !== false; + } + + onKeydown(event: KeyboardEvent) { + if (event.keyCode == KeyCodes.SPACE) { + event.preventDefault(); + this.toggle(event); + } + } + + toggle(event) { + if (this.disabled) { + event.stopPropagation(); + return; + } + + this.checked = !this.checked; + this.ngOnChange.emit(this.checked); + } +} diff --git a/ng2-material/components/content/content.scss b/ng2-material/components/content/content.scss new file mode 100644 index 00000000..0a64b5ab --- /dev/null +++ b/ng2-material/components/content/content.scss @@ -0,0 +1,36 @@ + +md-content { + + display: block; + position: relative; + overflow: auto; + -webkit-overflow-scrolling: touch; + + &[md-scroll-y] { + overflow-y: auto; + overflow-x: hidden; + } + &[md-scroll-x] { + overflow-x: auto; + overflow-y: hidden; + } + &[md-scroll-xy] { + } + + // For iOS allow disabling of momentum scrolling + // @see issue #2640. + + &.autoScroll { + -webkit-overflow-scrolling: auto; + } + +} + +// Theme + +md-content { + color: md-color($md-foreground, text); + background-color: md-color($md-background,A100); +} + + diff --git a/ng2-material/components/content/content.ts b/ng2-material/components/content/content.ts new file mode 100644 index 00000000..3c4c6b3e --- /dev/null +++ b/ng2-material/components/content/content.ts @@ -0,0 +1,22 @@ +import {Directive} from "angular2/core"; + +/** + * @name mdContent + * + * @description + * The `` directive is a container element useful for scrollable content + * + * @usage + * + * - Add the `[layout-padding]` attribute to make the content padded. + * + * + * + * Lorem ipsum dolor sit amet, ne quod novum mei. + * + * + * + */ +@Directive({selector: 'md-content'}) +export class MdContent { +} diff --git a/ng2-material/components/dialog/dialog.html b/ng2-material/components/dialog/dialog.html new file mode 100644 index 00000000..f965d6a8 --- /dev/null +++ b/ng2-material/components/dialog/dialog.html @@ -0,0 +1,3 @@ + + + diff --git a/ng2-material/components/dialog/dialog.scss b/ng2-material/components/dialog/dialog.scss new file mode 100644 index 00000000..50c6771c --- /dev/null +++ b/ng2-material/components/dialog/dialog.scss @@ -0,0 +1,27 @@ +.md-dialog { + position: absolute; + z-index: 80; + + /** Center the dialog. */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: 300px; + height: 300px; + + background-color: white; + border: 1px solid black; + box-shadow: 0 4px 4px;; + + padding: 20px; +} + +.md-backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.12); +} diff --git a/ng2-material/components/dialog/dialog.ts b/ng2-material/components/dialog/dialog.ts new file mode 100644 index 00000000..28161343 --- /dev/null +++ b/ng2-material/components/dialog/dialog.ts @@ -0,0 +1,276 @@ +import { + bind, + provide, + forwardRef, + Component, + ComponentRef, + Directive, + DynamicComponentLoader, + ElementRef, + Host, + Injectable, + ResolvedProvider, + SkipSelf, + Injector, + View, + ViewEncapsulation +} from 'angular2/core'; + +import {ObservableWrapper, Promise, PromiseWrapper} from 'angular2/src/facade/async'; +import {isPresent, Type} from 'angular2/src/facade/lang'; +import {DOM} from 'angular2/src/platform/dom/dom_adapter'; +import {MouseEvent, KeyboardEvent} from 'angular2/src/facade/browser'; +import {KeyCodes} from '../../core/key_codes'; + +// TODO(jelbourn): Opener of dialog can control where it is rendered. +// TODO(jelbourn): body scrolling is disabled while dialog is open. +// TODO(jelbourn): Don't manually construct and configure a DOM element. See #1402 +// TODO(jelbourn): Wrap focus from end of dialog back to the start. Blocked on #1251 +// TODO(jelbourn): Focus the dialog element when it is opened. +// TODO(jelbourn): Real dialog styles. +// TODO(jelbourn): Pre-built `alert` and `confirm` dialogs. +// TODO(jelbourn): Animate dialog out of / into opening element. + +/** + * Service for opening modal dialogs. + */ +@Injectable() +export class MdDialog { + componentLoader: DynamicComponentLoader; + + constructor(loader: DynamicComponentLoader) { + this.componentLoader = loader; + } + + /** + * Opens a modal dialog. + * @param type The component to open. + * @param elementRef The logical location into which the component will be opened. + * @param options + * @returns Promise for a reference to the dialog. + */ + open(type: Type, elementRef: ElementRef, options: MdDialogConfig = null): Promise { + var config = isPresent(options) ? options : new MdDialogConfig(); + + // Create the dialogRef here so that it can be injected into the content component. + var dialogRef = new MdDialogRef(); + + var bindings = Injector.resolve([provide(MdDialogRef, {useValue: dialogRef})]); + + var backdropRefPromise = this._openBackdrop(elementRef, bindings); + + // First, load the MdDialogContainer, into which the given component will be loaded. + return this.componentLoader.loadNextToLocation(MdDialogContainer, elementRef) + .then(containerRef => { + // TODO(tbosch): clean this up when we have custom renderers + // (https://github.com/angular/angular/issues/1807) + // TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element + // directly on the document body (also needed for web workers stuff). + // Create a DOM node to serve as a physical host element for the dialog. + var dialogElement = containerRef.location.nativeElement; + DOM.appendChild(DOM.query('body'), dialogElement); + + // TODO(jelbourn): Do this with hostProperties (or another rendering abstraction) once + // ready. + if (isPresent(config.width)) { + DOM.setStyle(dialogElement, 'width', config.width); + } + if (isPresent(config.height)) { + DOM.setStyle(dialogElement, 'height', config.height); + } + + dialogRef.containerRef = containerRef; + + // Now load the given component into the MdDialogContainer. + return this.componentLoader.loadNextToLocation(type, containerRef.instance.contentRef, + bindings) + .then(contentRef => { + + // Wrap both component refs for the container and the content so that we can return + // the `instance` of the content but the dispose method of the container back to the + // opener. + dialogRef.contentRef = contentRef; + containerRef.instance.dialogRef = dialogRef; + + backdropRefPromise.then(backdropRef => { + dialogRef.whenClosed.then((_) => { backdropRef.dispose(); }); + }); + + return dialogRef; + }); + }); + } + + /** Loads the dialog backdrop (transparent overlay over the rest of the page). */ + _openBackdrop(elementRef: ElementRef, bindings: ResolvedProvider[]): Promise { + return this.componentLoader.loadNextToLocation(MdBackdrop, elementRef, bindings) + .then((componentRef) => { + // TODO(tbosch): clean this up when we have custom renderers + // (https://github.com/angular/angular/issues/1807) + var backdropElement = componentRef.location.nativeElement; + DOM.addClass(backdropElement, 'md-backdrop'); + DOM.appendChild(DOM.query('body'), backdropElement); + return componentRef; + }); + } + + alert(message: string, okMessage: string): Promise { + throw 'Not implemented'; + } + + confirm(message: string, okMessage: string, cancelMessage: string): Promise { + throw 'Not implemented'; + } +} + + +/** + * Reference to an opened dialog. + */ +export class MdDialogRef { + // Reference to the MdDialogContainer component. + containerRef: ComponentRef; + + // Reference to the Component loaded as the dialog content. + _contentRef: ComponentRef; + + // Whether the dialog is closed. + isClosed: boolean; + + // Deferred resolved when the dialog is closed. The promise for this deferred is publicly exposed. + whenClosedDeferred: any; + + // Deferred resolved when the content ComponentRef is set. Only used internally. + contentRefDeferred: any; + + constructor() { + this._contentRef = null; + this.containerRef = null; + this.isClosed = false; + + this.contentRefDeferred = PromiseWrapper.completer(); + this.whenClosedDeferred = PromiseWrapper.completer(); + } + + set contentRef(value: ComponentRef) { + this._contentRef = value; + this.contentRefDeferred.resolve(value); + } + + /** Gets the component instance for the content of the dialog. */ + get instance() { + if (isPresent(this._contentRef)) { + return this._contentRef.instance; + } + + // The only time one could attempt to access this property before the value is set is if an + // access occurs during + // the constructor of the very instance they are trying to get (which is much more easily + // accessed as `this`). + throw "Cannot access dialog component instance *from* that component's constructor."; + } + + + /** Gets a promise that is resolved when the dialog is closed. */ + get whenClosed(): Promise { + return this.whenClosedDeferred.promise; + } + + /** Closes the dialog. This operation is asynchronous. */ + close(result: any = null) { + this.contentRefDeferred.promise.then((_) => { + if (!this.isClosed) { + this.isClosed = true; + this.containerRef.dispose(); + this.whenClosedDeferred.resolve(result); + } + }); + } +} + +/** Confiuration for a dialog to be opened. */ +export class MdDialogConfig { + width: string; + height: string; + + constructor() { + // Default configuration. + this.width = null; + this.height = null; + } +} + +/** + * Container for user-provided dialog content. + */ +@Component({ + selector: 'md-dialog-container', + host: { + 'class': 'md-dialog', + 'tabindex': '0', + '(body:keydown)': 'documentKeypress($event)', + }, +}) +@View({ + encapsulation: ViewEncapsulation.None, + templateUrl: 'ng2-material/components/dialog/dialog.html', + directives: [forwardRef(() => MdDialogContent)] +}) +class MdDialogContainer { + // Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content. + contentRef: ElementRef; + + // Ref to the open dialog. Used to close the dialog based on certain events. + dialogRef: MdDialogRef; + + constructor() { + this.contentRef = null; + this.dialogRef = null; + } + + wrapFocus() { + // Return the focus to the host element. Blocked on #1251. + } + + documentKeypress(event: KeyboardEvent) { + if (event.keyCode == KeyCodes.ESCAPE) { + this.dialogRef.close(); + } + } +} + +/** + * Simple decorator used only to communicate an ElementRef to the parent MdDialogContainer as the + * location + * for where the dialog content will be loaded. + */ +@Directive({ + selector: 'md-dialog-content', +}) +class MdDialogContent { + constructor(@Host() @SkipSelf() dialogContainer: MdDialogContainer, elementRef: ElementRef) { + dialogContainer.contentRef = elementRef; + } +} + +/** Component for the dialog "backdrop", a transparent overlay over the rest of the page. */ +@Component({ + selector: 'md-backdrop', + host: { + '(click)': 'onClick()', + }, +}) +@View({template: '', encapsulation: ViewEncapsulation.None}) +class MdBackdrop { + dialogRef: MdDialogRef; + + constructor(dialogRef: MdDialogRef) { + this.dialogRef = dialogRef; + } + + onClick() { + // TODO(jelbourn): Use MdDialogConfig to capture option for whether dialog should close on + // clicking outside. + this.dialogRef.close(); + } +} diff --git a/ng2-material/components/divider/divider.scss b/ng2-material/components/divider/divider.scss new file mode 100644 index 00000000..2c90eff6 --- /dev/null +++ b/ng2-material/components/divider/divider.scss @@ -0,0 +1,31 @@ +md-divider { + display: block; + border-top-width: 1px; + border-top-style: solid; + margin: 0; + + &[md-inset] { + margin-left: $baseline-grid * 10; + } +} + +.layout-row { + & > md-divider { + border-top-width: 0; + border-right-width: 1px; + border-right-style: solid; + + } +} + +// Theme + +md-divider { + border-top-color: md-color($md-foreground,divider); +} + +.layout-row { + & > md-divider { + border-right-color: md-color($md-foreground,divider); + } +} diff --git a/ng2-material/components/divider/divider.ts b/ng2-material/components/divider/divider.ts new file mode 100644 index 00000000..9ee42e1b --- /dev/null +++ b/ng2-material/components/divider/divider.ts @@ -0,0 +1,22 @@ +import {Component,View, ElementRef} from "angular2/core"; +import {ViewEncapsulation} from "angular2/core"; + +/** + * @name mdDivider + * + * @description + * Dividers group and separate content within lists and page layouts using strong visual and spatial distinctions. This divider is a thin rule, lightweight enough to not distract the user from content. + * + * @param {boolean=} md-inset Add this attribute to activate the inset divider style. + * @usage + * + * + * + * + * + * + */ +@Component({selector: 'md-divider'}) +@View({template: '', encapsulation: ViewEncapsulation.None}) +export class MdDivider { +} diff --git a/ng2-material/components/grid_list/grid_list.html b/ng2-material/components/grid_list/grid_list.html new file mode 100644 index 00000000..6a1d43b5 --- /dev/null +++ b/ng2-material/components/grid_list/grid_list.html @@ -0,0 +1,3 @@ + + + diff --git a/ng2-material/components/grid_list/grid_list.scss b/ng2-material/components/grid_list/grid_list.scss new file mode 100644 index 00000000..fb8d0acf --- /dev/null +++ b/ng2-material/components/grid_list/grid_list.scss @@ -0,0 +1,68 @@ +md-grid-list { + display: block; + position: relative; +} + +md-grid-tile { + display: block; + position: absolute; + + figure { + display: flex; + position: absolute; + + align-items: center; + justify-content: center; + height: 100%; + + top: 0; + right: 0; + bottom: 0; + left: 0; + + padding: 0; + margin: 0; + } + + // Headers & footers + md-grid-tile-header, + md-grid-tile-footer { + display: flex; + flex-direction: row; + align-items: center; + height: 48px; + color: #fff; + background: rgba(0, 0, 0, 0.18); + overflow: hidden; + + // Positioning + position: absolute; + left: 0; + right: 0; + + h3, + h4 { + font-weight: 400; + margin: 0 0 0 16px; + } + + h3 { + font-size: 14px; + } + + h4 { + font-size: 12px; + } + } + + md-grid-tile-header { + top: 0; + } + + md-grid-tile-footer { + bottom: 0; + } +} + + + diff --git a/ng2-material/components/grid_list/grid_list.ts b/ng2-material/components/grid_list/grid_list.ts new file mode 100644 index 00000000..3a3c81ae --- /dev/null +++ b/ng2-material/components/grid_list/grid_list.ts @@ -0,0 +1,428 @@ +import { + Component, + View, + ViewEncapsulation, + Host, + SkipSelf, + OnChanges, + OnDestroy, + AfterContentChecked +} from 'angular2/angular2'; + +import {ListWrapper} from 'angular2/src/facade/collection'; +import {StringWrapper, isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang'; +import {Math} from 'angular2/src/facade/math'; + +// TODO(jelbourn): Set appropriate aria attributes for grid list elements. +// TODO(jelbourn): Animations. +// TODO(jelbourn): Conditional (responsive) column count / row size. +// TODO(jelbourn): Re-layout on window resize / media change (debounced). +// TODO(jelbourn): gridTileHeader and gridTileFooter. + +/** Row hieght mode options. Use a static class b/c TypeScript enums are strictly number-based. */ +class RowHeightMode { + static FIT = 'fit'; + static FIXED = 'fixed'; + static RATIO = 'ratio'; +} + + +@Component({selector: 'md-grid-list', inputs: ['cols', 'rowHeight', 'gutterSize']}) +@View({ + templateUrl: 'ng2-material/components/grid_list/grid_list.html', + encapsulation: ViewEncapsulation.None +}) +export class MdGridList implements AfterContentChecked { + /** Array of tiles that are being rendered. */ + tiles: MdGridTile[]; + + /** Number of columns being rendered. */ + _cols: number; + + /** Number of rows being rendered (computed). */ + rows: number; + + /** Mode used to determine row heights. See RowHeightMode. */ + rowHeightMode: string; + + /** Fixed row height, as given by the user. Only used for 'fixed' mode. */ + fixedRowHeight: string; + + /** Ratio width:height given by user to determine row height. Only used for 'ratio' mode.*/ + rowHeightRatio: number; + + /** The amount of space between tiles. This will be something like '5px' or '2em'. */ + gutterSize: string; + + constructor() { + this.tiles = []; + this.rows = 0; + } + + set cols(value: any) { + this._cols = isString(value) ? NumberWrapper.parseInt(value, 10) : value; + } + + get cols() { + return this._cols; + } + + /** Set internal representation of row height from the user-provided value. */ + set rowHeight(value) { + if (value === RowHeightMode.FIT) { + this.rowHeightMode = RowHeightMode.FIT; + } else if (StringWrapper.contains(value, ':')) { + let ratioParts = value.split(':'); + if (ratioParts.length !== 2) { + throw `md-grid-list: invalid ratio given for row-height: "${value}"`; + } + + this.rowHeightMode = RowHeightMode.RATIO; + this.rowHeightRatio = + NumberWrapper.parseFloat(ratioParts[0]) / NumberWrapper.parseFloat(ratioParts[1]); + } else { + this.rowHeightMode = RowHeightMode.FIXED; + this.fixedRowHeight = value; + } + } + + ngAfterContentChecked() { + this.layoutTiles(); + } + + /** Computes and applies the size and position for all children grid tiles. */ + layoutTiles() { + let tracker = new TileCoordinator(this.cols, this.tiles); + this.rows = tracker.rowCount; + + for (let i = 0; i < this.tiles.length; i++) { + let pos = tracker.positions[i]; + let tile = this.tiles[i]; + tile.style = this.getTileStyle(tile, pos.row, pos.col); + } + } + + /** + * Adds a tile to the grid-list. + * @param tile + */ + addTile(tile: MdGridTile) { + this.tiles.push(tile); + } + + /** + * Removes a tile from the grid-list. + * @param tile + */ + removeTile(tile: MdGridTile) { + ListWrapper.remove(this.tiles, tile); + } + + /** + * Computes the amount of space a single 1x1 tile would take up (width or height). + * Used as a basis for other calculations. + * @param sizePercent Percent of the total grid-list space that one 1x1 tile would take up. + * @param gutterFraction Fraction of the gutter size taken up by one 1x1 tile. + * @return The size of a 1x1 tile as an expression that can be evaluated via CSS calc(). + */ + getBaseTileSize(sizePercent: number, gutterFraction: number): string { + // Take the base size percent (as would be if evenly dividing the size between cells), + // and then subtracting the size of one gutter. However, since there are no gutters on the + // edges, each tile only uses a fration (gutterShare = numGutters / numCells) of the gutter + // size. (Imagine having one gutter per tile, and then breaking up the extra gutter on the + // edge evenly among the cells). + return `(${sizePercent}% - ( ${this.gutterSize} * ${gutterFraction} ))`; + } + + + /** + * Gets The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value. + * @param offset Number of tiles that have already been rendered in the row/column. + * @param baseSize Base size of a 1x1 tile (as computed in getBaseTileSize). + * @return Position of the tile as a CSS calc() expression. + */ + getTilePosition(baseSize: string, offset: number): string { + // The position comes the size of a 1x1 tile plus gutter for each previous tile in the + // row/column (offset). + return `calc( (${baseSize} + ${this.gutterSize}) * ${offset} )`; + } + + + /** + * Gets the actual size of a tile, e.g., width or height, taking rowspan or colspan into account. + * @param baseSize Base size of a 1x1 tile (as computed in getBaseTileSize). + * @param span The tile's rowspan or colspan. + * @return Size of the tile as a CSS calc() expression. + */ + getTileSize(baseSize: string, span: number): string { + return `calc( (${baseSize} * ${span}) + (${span - 1} * ${this.gutterSize}) )`; + } + + + /** Gets the style properties to be applied to a tile for the given row and column index. */ + getTileStyle(tile: MdGridTile, rowIndex: number, colIndex: number): TileStyle { + // Percent of the available horizontal space that one column takes up. + let percentWidthPerTile = 100 / this.cols; + + // Fraction of the vertical gutter size that each column takes up. + // For example, if there are 5 columns, each column uses 4/5 = 0.8 times the gutter width. + let gutterWidthFractionPerTile = (this.cols - 1) / this.cols; + + // Base horizontal size of a column. + let baseTileWidth = this.getBaseTileSize(percentWidthPerTile, gutterWidthFractionPerTile); + + // The width and horizontal position of each tile is always calculated the same way, but the + // height and vertical position depends on the rowMode. + let tileStyle = new TileStyle(); + tileStyle.left = this.getTilePosition(baseTileWidth, colIndex); + tileStyle.width = this.getTileSize(baseTileWidth, tile.colspan); + + if (this.rowHeightMode == RowHeightMode.FIXED) { + // In fixed mode, simply use the given row height. + tileStyle.top = this.getTilePosition(this.fixedRowHeight, rowIndex); + tileStyle.height = this.getTileSize(this.fixedRowHeight, tile.rowspan); + } + + if (this.rowHeightMode == RowHeightMode.RATIO) { + let percentHeightPerTile = percentWidthPerTile / this.rowHeightRatio; + let baseTileHeight = this.getBaseTileSize(percentHeightPerTile, gutterWidthFractionPerTile); + + // Use paddingTop and marginTop to maintain the given aspect ratio, as + // a percentage-based value for these properties is applied versus the *width* of the + // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties + tileStyle.marginTop = this.getTilePosition(baseTileHeight, rowIndex); + tileStyle.paddingTop = this.getTileSize(baseTileHeight, tile.rowspan); + } + + if (this.rowHeightMode == RowHeightMode.FIT) { + // Percent of the available vertical space that one row takes up. + let percentHeightPerTile = 100 / this.cols; + + // Fraction of the horizontal gutter size that each column takes up. + let gutterHeightFractionPerTile = (this.rows - 1) / this.rows; + + // Base vertical size of a column. + let baseTileHeight = this.getBaseTileSize(percentHeightPerTile, gutterHeightFractionPerTile); + + tileStyle.top = this.getTilePosition(baseTileHeight, rowIndex); + tileStyle.height = this.getTileSize(baseTileHeight, tile.rowspan); + } + + return tileStyle; + } +} + +@Component({ + selector: 'md-grid-tile', + inputs: ['rowspan', 'colspan'], + host: { + 'role': 'listitem', + '[style.height]': 'style.height', + '[style.width]': 'style.width', + '[style.top]': 'style.top', + '[style.left]': 'style.left', + '[style.marginTop]': 'style.marginTop', + '[style.paddingTop]': 'style.paddingTop', + } +}) +@View({ + templateUrl: 'ng2-material/components/grid_list/grid_tile.html', + encapsulation: ViewEncapsulation.None +}) +export class MdGridTile implements OnDestroy, + OnChanges { + gridList: MdGridList; + _rowspan: number; + _colspan: number; + + style: TileStyle; + isRegisteredWithGridList: boolean; + + constructor(@SkipSelf() @Host() gridList: MdGridList) { + this.gridList = gridList; + + // Tiles default to 1x1, but rowspan and colspan can be changed via binding. + this.rowspan = 1; + this.colspan = 1; + this.style = new TileStyle(); + } + + set rowspan(value) { + this._rowspan = isString(value) ? NumberWrapper.parseInt(value, 10) : value; + } + + get rowspan() { + return this._rowspan; + } + + set colspan(value) { + this._colspan = isString(value) ? NumberWrapper.parseInt(value, 10) : value; + } + + get colspan() { + return this._colspan; + } + + /** + * Change handler invoked when bindings are resolved or when bindings have changed. + * Notifies grid-list that a re-layout is required. + */ + ngOnChanges(_) { + if (!this.isRegisteredWithGridList) { + this.gridList.addTile(this); + this.isRegisteredWithGridList = true; + } + } + + /** + * Destructor function. Deregisters this tile from the containing grid-list. + */ + ngOnDestroy() { + this.gridList.removeTile(this); + } +} + + +/** + * Class for determining, from a list of tiles, the (row, col) position of each of those tiles + * in the grid. This is necessary (rather than just rendering the tiles in normal document flow) + * because the tiles can have a rowspan. + * + * The positioning algorithm greedily places each tile as soon as it encounters a gap in the grid + * large enough to accomodate it so that the tiles still render in the same order in which they + * are given. + * + * The basis of the algorithm is the use of an array to track the already placed tiles. Each + * element of the array corresponds to a column, and the value indicates how many cells in that + * column are already occupied; zero indicates an empty cell. Moving "down" to the next row + * decrements each value in the tracking array (indicating that the column is one cell closer to + * being free). + */ +class TileCoordinator { + // Tracking array (see class description). + tracker: number[]; + + // Index at which the search for the next gap will start. + columnIndex: number; + + // The current row index. + rowIndex: number; + + // The computed (row, col) position of each tile (the output). + positions: Position[]; + + constructor(numColumns: number, tiles: MdGridTile[]) { + this.columnIndex = 0; + this.rowIndex = 0; + + this.tracker = ListWrapper.createFixedSize(numColumns); + ListWrapper.fill(this.tracker, 0); + + this.positions = tiles.map(tile => this._trackTile(tile)); + } + + /** Gets the number of rows occupied by tiles. */ + get rowCount() { + return this.rowIndex + 1; + } + + _trackTile(tile: MdGridTile): Position { + if (tile.colspan > this.tracker.length) { + throw `Tile with colspan ${tile.colspan} is wider + than grid with cols="${this.tracker.length}".` + } + + // Start index is inclusive, end index is exclusive. + let gapStartIndex = -1; + let gapEndIndex = -1; + + // Look for a gap large enough to fit the given tile. Empty spaces are marked with a zero. + do { + // If we've reached the end of the row, go to the next row + if (this.columnIndex + tile.colspan > this.tracker.length) { + this._nextRow(); + continue; + } + + gapStartIndex = ListWrapper.indexOf(this.tracker, 0, this.columnIndex); + + // If there are no more empty spaces in this row at all, move on to the next row. + if (gapStartIndex == -1) { + this._nextRow(); + continue; + } + + gapEndIndex = this._findGapEndIndex(gapStartIndex); + + // If a gap large enough isn't found, we want to start looking immediately after the current + // gap on the next iteration. + this.columnIndex = gapStartIndex + 1; + + // Continue iterating until we find a gap wide enough for this tile. + } while (gapEndIndex - gapStartIndex < tile.colspan); + + // We now have a space big enough for this tile, so place it. + this._markTilePosition(gapStartIndex, tile); + + // The next time we look for a gap, the search will start at columnIndex, which should be + // immediately after the tile that has just been placed. + this.columnIndex = gapStartIndex + tile.colspan; + + return new Position(this.rowIndex, gapStartIndex); + } + + /** Move "down" to the next row. */ + _nextRow() { + this.columnIndex = 0; + this.rowIndex++; + + // Decrement all spaces by one to reflect moving down one row. + for (let i = 0; i < this.tracker.length; i++) { + this.tracker[i] = Math.max(0, this.tracker[i] - 1); + } + } + + /** + * Finds the end index (exclusive) of a gap given the index from which to start looking. + * The gap ends when a non-zero value is found. + */ + _findGapEndIndex(gapStartIndex: number): number { + for (let i = gapStartIndex + 1; i < this.tracker.length; i++) { + if (this.tracker[i] != 0) { + return i; + } + } + + // The gap ends with the end of the row. + return this.tracker.length; + } + + /** Update the tile tracker to account for the given tile in the given space. */ + _markTilePosition(start, tile) { + for (let i = 0; i < tile.colspan; i++) { + this.tracker[start + i] = tile.rowspan; + } + } +} + +/** Simple data structure for tile position (row, col). */ +class Position { + row: number; + col: number; + + constructor(row: number, col: number) { + this.row = row; + this.col = col; + } +} + + +/** Simple data structure for style values to be applied to a tile. */ +class TileStyle { + height: string; + width: string; + top: string; + left: string; + marginTop: string; + paddingTop: string; +} diff --git a/ng2-material/components/grid_list/grid_tile.html b/ng2-material/components/grid_list/grid_tile.html new file mode 100644 index 00000000..8fb81735 --- /dev/null +++ b/ng2-material/components/grid_list/grid_tile.html @@ -0,0 +1,5 @@ + + + + + diff --git a/ng2-material/components/icon/icon.html b/ng2-material/components/icon/icon.html new file mode 100644 index 00000000..0d8b32a2 --- /dev/null +++ b/ng2-material/components/icon/icon.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/ng2-material/components/icon/icon.scss b/ng2-material/components/icon/icon.scss new file mode 100644 index 00000000..de661f88 --- /dev/null +++ b/ng2-material/components/icon/icon.scss @@ -0,0 +1,22 @@ +@import '../../core/style/variables'; + +$icon-size: rem(2.4); + +md-icon { + margin: auto; + background-repeat: no-repeat no-repeat; + display: inline-block; + vertical-align: middle; + fill: currentColor; + height: $icon-size; + width: $icon-size; + + svg { + pointer-events: none; + } + + &[md-font-icon] { + line-height: 1; + width: auto; + } +} diff --git a/ng2-material/components/icon/icon.ts b/ng2-material/components/icon/icon.ts new file mode 100644 index 00000000..463151fe --- /dev/null +++ b/ng2-material/components/icon/icon.ts @@ -0,0 +1,12 @@ +import {Directive, ElementRef} from 'angular2/core'; +import {DOM} from 'angular2/src/platform/dom/dom_adapter'; + + +@Directive({ + selector: '[md-icon], .md-icon' +}) +export class MdIcon { + constructor(element: ElementRef) { + DOM.addClass(element.nativeElement, 'material-icons'); + } +} diff --git a/ng2-material/components/input/input.scss b/ng2-material/components/input/input.scss new file mode 100644 index 00000000..c6d14c82 --- /dev/null +++ b/ng2-material/components/input/input.scss @@ -0,0 +1,274 @@ +@import "../../core/style/variables"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +@mixin input-placeholder-color($color) { + &::-webkit-input-placeholder, + &::-moz-placeholder, /* Firefox 19+ */ + &:-moz-placeholder, /* Firefox 18- */ + &:-ms-input-placeholder { + color: $color; + } +} + +$input-container-padding: 2px !default; + +$input-label-default-offset: 24px !default; +$input-label-default-scale: 1.0 !default; +$input-label-float-offset: 4px !default; +$input-label-float-scale: 0.75 !default; + +$input-placeholder-offset: $input-label-default-offset !default; + +$input-border-width-default: 1px !default; +$input-border-width-focused: 2px !default; +$input-line-height: 26px !default; +$input-padding-top: 2px !default; + +$input-full-width-line-height: 16px !default; +$input-full-width-padding-top: 16px !default; + +$input-error-font-size: 12px !default; +$input-error-height: 24px !default; + +md-input-container { + box-sizing: border-box; + display: flex; + position: relative; + flex-direction: column; + + overflow-x: hidden; + padding: $input-container-padding; + padding-bottom: $input-container-padding + $input-error-height; + + > md-icon { + position: absolute; + top: 5px; + left: 2px; + + input { + margin-left: 28px * 2; + } + } + + textarea, + input[type="text"], + input[type="password"], + input[type="datetime"], + input[type="datetime-local"], + input[type="date"], + input[type="month"], + input[type="time"], + input[type="week"], + input[type="number"], + input[type="email"], + input[type="url"], + input[type="search"], + input[type="tel"], + input[type="color"] { + // Remove default appearance from all input / textarea. + // `appearance` is not supported in IE, but does IE apply any txt input styling? + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + } + + textarea { + resize: none; + overflow: hidden; + } + + textarea.md-input { + min-height: 2 * $input-line-height + $input-border-width-focused + $input-padding-top; + -ms-flex-preferred-size: auto; // IE10 + } + + label:not(.md-no-float) { + order: 1; + pointer-events: none; + -webkit-font-smoothing: antialiased; + padding-left: $input-container-padding; + z-index: 1; + transform: translate3d(0, $input-label-default-offset, 0) scale($input-label-default-scale); + transform-origin: left top; + transition: transform $swift-ease-out-timing-function 0.25s; + } + + .md-placeholder { + position: absolute; + top: 0; + opacity: 0; + transition-property: opacity, transform; + transform: translate3d(0, $input-placeholder-offset + $baseline-grid * 0.75, 0); + } + &.md-full-width { + padding-bottom: 0; + } + &.md-input-focused .md-placeholder { + opacity: 1; + transform: translate3d(0, $input-placeholder-offset, 0); + } + // Placeholder should immediately disappear when the user starts typing + &.md-input-has-value .md-placeholder { + transition: none; + opacity: 0; + } + + &:not( .md-input-has-value ) input:not( :focus ) { + color: transparent; + } + + + /* + * The .md-input class is added to the input/textarea + */ + .md-input { + flex: 1 1 auto; + order: 2; + display: block; + + background: none; + padding: $input-padding-top 2px $input-border-width-focused - $input-border-width-default; + border-width: 0 0 $input-border-width-default 0; + line-height: $input-line-height; + -ms-flex-preferred-size: $input-line-height; // IE10 + border-radius: 0; + + &:focus { + outline: none; + } + &:invalid { + outline: none; + box-shadow: none; + } + } + &.md-full-width { + .md-input { + padding: $input-full-width-padding-top 2px; + line-height: $input-full-width-line-height; + -ms-flex-preferred-size: $input-full-width-line-height; // IE10 + } + } + + .md-char-counter { + -webkit-font-smoothing: antialiased; + position: absolute; + font-size: $input-error-font-size; + line-height: $input-error-height; + bottom: $input-container-padding; + right: $input-container-padding; + + // TODO(jelbourn): animations here. + } + + &.md-input-focused, + &.md-input-has-value { + label:not(.md-no-float) { + transform: translate3d(0,$input-label-float-offset,0) scale($input-label-float-scale); + } + } + + // Use wide border in error state or in focused state + &.md-input-focused .md-input &:not(.md-full-width) { + padding-bottom: 0; // Increase border width by 1px, decrease padding by 1 + border-width: 0 0 $input-border-width-focused 0; + } + + .md-input { + &[disabled], + [disabled] & { + background-position: 0 bottom; + // This background-size is coordinated with a linear-gradient set in input-theme.scss + // to create a dotted line under the input. + background-size: 3px 1px; + background-repeat: repeat-x; + } + } +} + + +// THEME + +md-input-container { + .md-input { + @include input-placeholder-color(md-color($md-foreground, hint-text)); + color: md-color($md-foreground, text); + border-color: md-color($md-foreground, divider); + // text-shadow: md-color($md-foreground, shadow); // what is this? + } + + > md-icon { + color: md-color($md-foreground, text); + } + + label, + .md-placeholder { + // text-shadow: md-color($md-foreground, shadow); // what is this? + color: md-color($md-foreground, hint-text); + } + + div[ng-messages] { + color: md-color($md-warn, 500) + } + + + &:not(.md-input-invalid) { + &.md-input-has-value { + label { + color: md-color($md-foreground, secondary-text); + } + } + &.md-input-focused { + .md-input { + border-color: md-color($md-primary, 500); + } + label { + color: md-color($md-primary, 500); + } + md-icon { + color: md-color($md-primary, 500); + } + &.md-accent { + .md-input { + border-color: md-color($md-accent, 500); + } + label { + color: md-color($md-accent, 500); + } + } + &.md-warn { + .md-input { + border-color: md-color($md-warn, 500); + } + label { + color: md-color($md-warn, 500); + } + } + } + } + &.md-input-invalid { + .md-input { + border-color: md-color($md-warn, 500); + } + label { + color: md-color($md-warn, 500); + } + .md-char-counter { + color: md-color($md-warn, 500); + } + } + &.md-full-width { + .md-input { + border-color: transparent !important; + } + } + .md-input { + &[disabled], + [disabled] & { + border-bottom-color: transparent; + color: md-color($md-foreground, disabled-text); + background-image: linear-gradient(to right, md-color($md-foreground, divider) 0%, md-color($md-foreground, divider) 33%, transparent 0%); + background-image: -ms-linear-gradient(left, transparent 0%, md-color($md-foreground, divider) 100%); + } + } +} diff --git a/ng2-material/components/input/input.ts b/ng2-material/components/input/input.ts new file mode 100644 index 00000000..c33e6c1f --- /dev/null +++ b/ng2-material/components/input/input.ts @@ -0,0 +1,89 @@ +import {Directive, Attribute, Host, SkipSelf, AfterContentChecked, ElementRef} from 'angular2/angular2'; + +import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; +import {Input,Output} from 'angular2/core'; + +// TODO(jelbourn): validation (will depend on Forms API). +// TODO(jelbourn): textarea resizing +// TODO(jelbourn): max-length counter +// TODO(jelbourn): placeholder property + +@Directive({ + selector: 'md-input-container', + host: { + '[class.md-input-has-value]': 'inputHasValue', + '[class.md-input-focused]': 'inputHasFocus', + } +}) +export class MdInputContainer implements AfterContentChecked { + // The MdInput or MdTextarea inside of this container. + _input: MdInput = null; + + // Whether the input inside of this container has a non-empty value. + inputHasValue: boolean = false; + + // Whether the input inside of this container has focus. + inputHasFocus: boolean = false; + + constructor(@Attribute('id') id: string) { + } + + ngAfterContentChecked() { + // Enforce that this directive actually contains a text input. + if (this._input === null) { + throw 'No or found inside of '; + } + } + + /** Registers the child MdInput or MdTextarea. */ + registerInput(input) { + if (this._input !== null) { + throw 'Only one text input is allowed per .'; + } + + this._input = input; + this.inputHasValue = input.value != ''; + + // Listen to input changes and focus events so that we can apply the appropriate CSS + // classes based on the input state. + ObservableWrapper.subscribe(input.mdChange, value => { this.inputHasValue = value != ''; }); + + ObservableWrapper.subscribe(input.mdFocusChange, + hasFocus => this.inputHasFocus = hasFocus); + } +} + + +@Directive({ + selector: 'md-input-container input', + host: { + 'class': 'md-input', + '(input)': 'updateValue($event)', + '(focus)': 'setHasFocus(true)', + '(blur)': 'setHasFocus(false)' + } +}) +export class MdInput { + @Input() value: string; + + // Events emitted by this directive. We use these special 'md-' events to communicate + // to the parent MdInputContainer. + @Output() mdChange: EventEmitter = new EventEmitter(); + @Output() mdFocusChange: EventEmitter = new EventEmitter(); + + constructor(@Attribute('value') value: string, @SkipSelf() @Host() container: MdInputContainer, + @Attribute('id') id: string) { + this.value = value == null ? '' : value; + + container.registerInput(this); + } + + updateValue(event) { + this.value = event.target.value; + ObservableWrapper.callEmit(this.mdChange, this.value); + } + + setHasFocus(hasFocus: boolean) { + ObservableWrapper.callEmit(this.mdFocusChange, hasFocus); + } +} diff --git a/ng2-material/components/list/list.scss b/ng2-material/components/list/list.scss new file mode 100644 index 00000000..aca30a4f --- /dev/null +++ b/ng2-material/components/list/list.scss @@ -0,0 +1,308 @@ +$list-h3-margin: 0 0 0px 0 !default; +$list-h4-margin: 3px 0 1px 0 !default; +$list-h4-font-weight: 400 !default; +$list-header-line-height: 1.2em !default; +$list-p-margin: 0 0 0 0 !default; +$list-p-line-height: 1.6em !default; + +$list-padding-top: $baseline-grid !default; +$list-padding-right: 0px !default; +$list-padding-left: 0px !default; +$list-padding-bottom: $baseline-grid !default; + +$item-padding-top: 0px !default; +$item-padding-right: 0px !default; +$item-padding-left: 0px !default; +$item-padding-bottom: 0px !default; +$list-item-padding-vertical: 0px !default; +$list-item-padding-horizontal: $baseline-grid * 2 !default; +$list-item-primary-width: $baseline-grid * 7 !default; +$list-item-primary-avatar-width: $baseline-grid * 5 !default; +$list-item-primary-icon-width: $baseline-grid * 3 !default; +$list-item-secondary-left-margin: $baseline-grid * 2 !default; +$list-item-secondary-button-width: $baseline-grid * 6 !default; +$list-item-inset-divider-offset: 12 * $baseline-grid !default; +$list-item-height: 6 * $baseline-grid !default; +$list-item-two-line-height: 9 * $baseline-grid !default; +$list-item-three-line-height: 11 * $baseline-grid !default; + +md-list { + display: block; + padding: $list-padding-top $list-padding-right $list-padding-bottom $list-padding-left; + + .md-subheader { + font-size: $body-font-size-base; + font-weight: 500; + letter-spacing: 0.010em; + line-height: $list-header-line-height; + } +} + +md-list-item { + // Ensure nested dividers are properly positioned + position: relative; + + &.md-proxy-focus.md-focused .md-no-style { + transition: background-color 0.15s linear; + } + &.md-no-proxy, + .md-no-style { + position: relative; + padding: $list-item-padding-vertical $list-item-padding-horizontal; + flex: 1 1 auto; + + &.md-button { + font-size: inherit; + height: inherit; + text-align: left; + text-transform: none; + width: 100%; + white-space: normal; + flex-direction: inherit; + align-items: inherit; + border-radius: 0; + + & > .md-ripple-container { + border-radius: 0; + } + } + &:focus { + outline: none + } + } + &.md-with-secondary { + position: relative; + } + &.md-clickable:hover { + cursor: pointer; + } + + md-divider { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + &[md-inset] { + left: $list-item-inset-divider-offset; + width: calc(100% - #{$list-item-inset-divider-offset}); + margin: 0; + } + } +} + +md-list-item, md-list-item .md-list-item-inner { + display: flex; + justify-content: flex-start; + align-items: center; + min-height: $list-item-height; + height:auto; + + // Layout for controls in primary or secondary divs, or auto-infered first child + & > div.md-primary > md-icon:not(.md-avatar-icon), + & > div.md-secondary > md-icon:not(.md-avatar-icon), + & > md-icon:first-child:not(.md-avatar-icon), + > md-icon.md-secondary:not(.md-avatar-icon) { + width: $list-item-primary-icon-width; + margin-top: 16px; + margin-bottom: 12px; + box-sizing: content-box; + } + & > div.md-primary > md-checkbox, + & > div.md-secondary > md-checkbox, + & > md-checkbox, + md-checkbox.md-secondary { + align-self: center; + .md-label { display: none; } + } + + & > md-icon:first-child:not(.md-avatar-icon) { + margin-right: $list-item-primary-width - $list-item-primary-icon-width; + } + & > md-checkbox { + width: 3 * $baseline-grid; + margin-left: 3px; + margin-right: 29px; + margin-top: 16px; + } + & .md-avatar, .md-avatar-icon { + margin-top: $baseline-grid; + margin-bottom: $baseline-grid; + margin-right: $list-item-primary-width - $list-item-primary-avatar-width; + border-radius: 50%; + box-sizing: content-box; + } + & .md-avatar { + width: $list-item-primary-avatar-width; + height: $list-item-primary-avatar-width; + } + & .md-avatar-icon { + padding: 8px; + } + + md-checkbox.md-secondary, + md-switch.md-secondary { + margin-top: 0; + margin-bottom: 0; + } + + md-checkbox.md-secondary { + margin-right: 0; + } + + md-switch.md-secondary { + margin-right: -6px; + } + + button.md-button.md-secondary-container { + background-color: transparent; + align-self: center; + border-radius: 50%; + margin: 0px; + min-width: 0px; + .md-ripple, + .md-ripple-container { + border-radius: 50%; + } + + &.md-icon-button { + // Make icon buttons align with checkboxes and other controls + margin-right: -12px; + } + } + + .md-secondary-container, + .md-secondary { + position: absolute; + top: 50%; + right: $list-item-padding-horizontal; + margin: 0 0 0 $list-item-secondary-left-margin; + transform: translate3d(0, -50%, 0); + } + + & > .md-button.md-secondary-container > .md-secondary { + margin-left: 0; + position: static; + } + + & > p, & > .md-list-item-inner > p { + flex: 1; + margin: 0; + } +} + + +md-list-item.md-2-line, +md-list-item.md-2-line > .md-no-style, +md-list-item.md-3-line, +md-list-item.md-3-line > .md-no-style { + align-items: flex-start; + justify-content: center; + + .md-list-item-text { + flex: 1; + margin: auto; + text-overflow: ellipsis; + + &.md-offset { + margin-left: $list-item-primary-width; + } + + h3 { + font-size: $subhead-font-size-base; + font-weight: 400; + letter-spacing: 0.010em; + margin: $list-h3-margin; + line-height: $list-header-line-height; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + h4 { + font-size: $body-font-size-base; + letter-spacing: 0.010em; + margin: $list-h4-margin; + font-weight: $list-h4-font-weight; + line-height: $list-header-line-height; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + p { + font-size: $body-font-size-base; + font-weight: 500; + letter-spacing: 0.010em; + margin: $list-p-margin; + line-height: $list-p-line-height; + } + } +} + +md-list-item.md-2-line, +md-list-item.md-2-line > .md-no-style { + height:auto; + min-height: $list-item-two-line-height; + &.md-long-text { + margin:1.6em; + } + + > .md-avatar, .md-avatar-icon { + margin-top: $baseline-grid * 1.5; + } + > md-icon:first-child { + align-self: flex-start; + } + .md-list-item-text { + flex: 1; + } +} + +md-list-item.md-3-line, +md-list-item.md-3-line > .md-no-style { + height:auto; + min-height: $list-item-three-line-height; + + &.md-long-text { + margin:1.6em; + } + + > md-icon:first-child, + > .md-avatar { + margin-top: $baseline-grid * 2; + } +} + + +// +// Theme +// + +md-list { + md-list-item.md-2-line .md-list-item-text, + md-list-item.md-3-line .md-list-item-text { + h3, h4 { + color: md-color($md-foreground,text);//'{{foreground-1}}'; + } + p { + color: md-color($md-foreground,secondary-text);//'{{foreground-2}}'; + } + } + .md-proxy-focus.md-focused div.md-no-style { + background-color: md-color($md-background,A100);//{{background-100}}'; + } + + md-list-item > .md-avatar-icon { + background-color: md-color($md-foreground,icon); //'{{foreground-3}}'; + color: md-color($md-background); //'{{background-color}}'; + } + + md-list-item > md-icon { + color: md-color($md-foreground,icon);//'{{foreground-2}}'; + + &.md-highlight { + color: md-color($md-primary);//'{{primary-color}}'; + &.md-accent { + color: md-color($md-accent);//'{{accent-color}}'; + } + } + } +} diff --git a/ng2-material/components/list/list.ts b/ng2-material/components/list/list.ts new file mode 100644 index 00000000..4c392592 --- /dev/null +++ b/ng2-material/components/list/list.ts @@ -0,0 +1,361 @@ +import {Directive} from "angular2/core"; +import {DOM} from 'angular2/src/platform/dom/dom_adapter'; +import {ElementRef} from "angular2/core"; +import {AfterViewInit} from "angular2/core"; +import {QueryList} from "angular2/core"; +import {Query} from "angular2/core"; +import {Component} from "angular2/core"; +import {ViewEncapsulation} from "angular2/core"; +import {Renderer} from "angular2/core"; +import {View} from "angular2/core"; +import {Attribute} from "angular2/core"; +import {DynamicComponentLoader, ComponentRef} from "angular2/core"; +import {Input} from "angular2/core"; + + +/** + * @name mdList + * + * @description + * The `` directive is a list container for 1..n `` tags. + * + * @usage + * + * + * + * + * + * {{item.title}} + * {{item.description}} + * + * + * + * + */ +@Directive({ + selector: 'md-list', + host: { + 'role': 'list' + } +}) +export class MdList { +} + +/** + * @ngdoc directive + * @name mdListItem + * @module material.components.list + * + * @restrict E + * + * @description + * The `` directive is a container intended for row items in a `` container. + * The `md-2-line` and `md-3-line` classes can be added to a `` + * to increase the height with 22px and 40px respectively. + * + * ## CSS + * `.md-avatar` - class for image avatars + * + * `.md-avatar-icon` - class for icon avatars + * + * `.md-offset` - on content without an avatar + * + * @usage + * + * + * + * + * Item content in list + * + * + * + * Item content in list + * + * + * + * + * _**Note:** We automatically apply special styling when the inner contents are wrapped inside + * of a `` tag. This styling is automatically ignored for `class="md-secondary"` buttons + * and you can include a class of `class="md-exclude"` if you need to use a non-secondary button + * that is inside the list, but does not wrap the contents._ + */ +@Component({ + selector: 'md-list-item', + host: { + 'role': 'listitem' + }, + properties: ['wrap'] +}) +@View({ + templateUrl: 'ng2-material/components/list/list_item.html' +}) + +export class MdListItem implements AfterViewInit { + + static PROXIED_TYPES: string[] = ['md-checkbox', 'md-switch']; + + public wrap: string = 'none'; + + /** + * True when the list item contents have been processed and optionally + * wrapped in a container. + */ + private _wrapped: boolean = false; + + constructor(private _element: ElementRef, + public dcl: DynamicComponentLoader, + @Attribute('href') private _href: string) { + } + + ngAfterViewInit(): any { + let el = this._element.nativeElement; + // Check for proxy controls (no ng-click on parent, and a control inside) + let secondaryItem = DOM.querySelector(el, '.md-secondary'); + let hasProxiedElement; + let proxyElement; + // WAS: tAttrs.ngIf || tAttrs.ngClick || tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref + let shouldButtonWrap = this._href; + if (shouldButtonWrap) { + this.wrapIn('button'); + } + else { + for (var i = 0, type; type = MdListItem.PROXIED_TYPES[i]; ++i) { + if (proxyElement = DOM.querySelector(el, type)) { + hasProxiedElement = true; + break; + } + } + if (hasProxiedElement) { + this.wrapIn('div'); + } + else if (!DOM.querySelector(el, '[md-button]:not(.md-secondary):not(.md-exclude)')) { + DOM.addClass(el, 'md-no-proxy'); + } + } + this.wrapSecondary(); + this.setupToggleAria(); + //var proxies = [], + // firstChild = DOM.firstChild()[0].firstElementChild, + // hasClick = firstChild && hasClickEvent(firstChild); + // + //computeProxies(); + //computeClickable(); + // + //if ($element.hasClass('md-proxy-focus') && proxies.length) { + // angular.forEach(proxies, (proxy) => { + // proxy = angular.element(proxy); + // + // $scope.mouseActive = false; + // proxy.on('mousedown', () => { + // $scope.mouseActive = true; + // $timeout(() => { + // $scope.mouseActive = false; + // }, 100); + // }) + // .on('focus', () => { + // if ($scope.mouseActive === false) { + // $element.addClass('md-focused'); + // } + // proxy.on('blur', () => { + // $element.removeClass('md-focused'); + // proxy.off('blur', proxyOnBlur); + // }); + // }); + // }); + //} + // + //function hasClickEvent(element) { + // var attr = element.attributes; + // for (var i = 0; i < attr.length; i++) { + // if ($attr.$normalize(attr[i].name) === 'ngClick') return true; + // } + // return false; + //} + // + //function computeProxies() { + // var children = $element.children(); + // if (children.length && !children[0].hasAttribute('ng-click')) { + // angular.forEach(proxiedTypes, function (type) { + // angular.forEach(firstChild.querySelectorAll(type), function (child) { + // proxies.push(child); + // }); + // }); + // } + //} + // + //function computeClickable() { + // if (proxies.length == 1 || hasClick) { + // $element.addClass('md-clickable'); + // + // if (!hasClick) { + // ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style'))); + // } + // } + //} + // + //if (!hasClick && !proxies.length) { + // firstChild && firstChild.addEventListener('keypress', function (e) { + // if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA') { + // var keyCode = e.which || e.keyCode; + // if (keyCode == $mdConstant.KEY_CODE.SPACE) { + // if (firstChild) { + // firstChild.click(); + // e.preventDefault(); + // e.stopPropagation(); + // } + // } + // } + // }); + //} + // + //$element.off('click'); + //$element.off('keypress'); + // + //if (proxies.length == 1 && firstChild) { + // $element.children().eq(0).on('click', function (e) { + // var parentButton = $mdUtil.getClosest(e.target, 'BUTTON'); + // if (!parentButton && firstChild.contains(e.target)) { + // angular.forEach(proxies, function (proxy) { + // if (e.target !== proxy && !proxy.contains(e.target)) { + // angular.element(proxy).triggerHandler('click'); + // } + // }); + // } + // }); + //} + } + + + // + // + // + setupToggleAria() { + let toggleTypes = ['md-switch', 'md-checkbox']; + let toggle; + let el = this._element.nativeElement; + + for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) { + if (toggle = DOM.querySelector(el, toggleType)) { + if (!toggle.hasAttribute('aria-label')) { + var p = DOM.querySelector(el, 'p'); + if (!p) return; + toggle.setAttribute('aria-label', 'Toggle ' + p.textContent); + } + } + } + } + + wrapIn(type) { + this.wrap = type; + let html = DOM.getInnerHTML(this._element.nativeElement); + @Component({}) + @View({template: html}) + class CompiledComponent { + } + this.dcl.loadIntoLocation(CompiledComponent, this._element, this.wrap) + .then((ref: ComponentRef) => { + console.log(`rendered component ${ref}`) + }); + + + //console.log("WRAP " + type); + //let el = this._element.nativeElement; + //var container; + //if (type == 'div') { + // container = DOM.createElement('div'); + // DOM.addClass(container, "md-no-style"); + // DOM.addClass(container, "md-list-item-inner"); + // DOM.setInnerHTML(container, DOM.getInnerHTML(el)); + // DOM.addClass(el, 'md-proxy-focus'); + //} + //else { + // container = DOM.createElement('button'); + // DOM.setAttribute(container, 'md-button', ''); + // DOM.addClass(container, "md-no-style"); + // DOM.setInnerHTML(container, ''); + // this.copyAttributes(el, container); + // DOM.setInnerHTML(DOM.querySelector(container, '.md-list-item-inner'), DOM.getInnerHTML(el)); + //} + //DOM.setAttribute(el, 'tabindex', '-1'); + //DOM.setInnerHTML(el, DOM.getOuterHTML(container)); + } + + wrapSecondary() { + //if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) { + // $mdAria.expect(secondaryItem, 'aria-label'); + // var buttonWrapper = angular.element(''); + // copyAttributes(secondaryItem, buttonWrapper[0]); + // secondaryItem.setAttribute('tabindex', '-1'); + // secondaryItem.classList.remove('md-secondary'); + // buttonWrapper.append(secondaryItem); + // secondaryItem = buttonWrapper[0]; + //} + // + //// Check for a secondary item and move it outside + //if (secondaryItem && ( + // secondaryItem.hasAttribute('ng-click') || + // ( tAttrs.ngClick && + // isProxiedElement(secondaryItem) ) + // )) { + // tEl.addClass('md-with-secondary'); + // tEl.append(secondaryItem); + //} + } + + copyAttributes(item, wrapper) { + //var copiedAttrs = ['ng-if', 'ng-click', 'aria-label', 'ng-disabled', + // 'ui-sref', 'href', 'ng-href', 'ng-attr-ui-sref']; + //angular.forEach(copiedAttrs, function (attr) { + // if (item.hasAttribute(attr)) { + // wrapper.setAttribute(attr, item.getAttribute(attr)); + // item.removeAttribute(attr); + // } + //}); + } + + isProxiedElement(el) { + //return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1; + } + + isButton(el) { + var nodeName = el.nodeName.toUpperCase(); + + return nodeName == "MD-BUTTON" || nodeName == "BUTTON"; + } + + +} + +@Component({ + selector: 'md-list-item[md-list-item-type=button]', + host: { + 'role': 'listitem' + } +}) +@View({ + template: `BOOOO` +}) +class MdListItemButton { + constructor() { + console.log("OWWWOOOOOO"); + } +} + + +/* + * @private + * @ngdoc controller + * @name MdListController + * @module source.components.list + * + */ +function MdListController($scope, $element, $mdListInkRipple) { + //var ctrl = this; + //ctrl.attachRipple = attachRipple; + // + //function attachRipple (scope, element) { + // var options = {}; + // $mdListInkRipple.attach(scope, element, options); + //} +} + diff --git a/ng2-material/components/list/list_item.html b/ng2-material/components/list/list_item.html new file mode 100644 index 00000000..6d0cd0f7 --- /dev/null +++ b/ng2-material/components/list/list_item.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/ng2-material/components/progress_circular/progress_circular.html b/ng2-material/components/progress_circular/progress_circular.html new file mode 100644 index 00000000..162cb601 --- /dev/null +++ b/ng2-material/components/progress_circular/progress_circular.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/ng2-material/components/progress_circular/progress_circular.scss b/ng2-material/components/progress_circular/progress_circular.scss new file mode 100644 index 00000000..e69de29b diff --git a/ng2-material/components/progress_circular/progress_circular.ts b/ng2-material/components/progress_circular/progress_circular.ts new file mode 100644 index 00000000..9b9d031d --- /dev/null +++ b/ng2-material/components/progress_circular/progress_circular.ts @@ -0,0 +1,10 @@ +import {Component, View, ViewEncapsulation} from 'angular2/angular2'; + +@Component({selector: 'md-progress-circular'}) +@View({ + templateUrl: 'ng2-material/components/progress_circular/progress_circular.html', + encapsulation: ViewEncapsulation.None +}) +export class MdProgressCircular { + constructor() {} +} diff --git a/ng2-material/components/progress_linear/progress_linear.html b/ng2-material/components/progress_linear/progress_linear.html new file mode 100644 index 00000000..f3f431f3 --- /dev/null +++ b/ng2-material/components/progress_linear/progress_linear.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/ng2-material/components/progress_linear/progress_linear.scss b/ng2-material/components/progress_linear/progress_linear.scss new file mode 100644 index 00000000..b332181d --- /dev/null +++ b/ng2-material/components/progress_linear/progress_linear.scss @@ -0,0 +1,266 @@ +@import "../../core/style/variables"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$progress-linear-bar-height: 5px !default; + +md-progress-linear { + display: block; + width: 100%; + height: $progress-linear-bar-height; + + *, *:before { + box-sizing: border-box; + } + + .md-progress-linear-container { + overflow: hidden; + position: relative; + height: $progress-linear-bar-height; + top: $progress-linear-bar-height; + transform: translate(0, 5px) scale(1, 0); + transition: all .3s linear; + } + + .md-progress-linear-container.md-ready { + transform: translate(0, 0) scale(1, 1); + } + + .md-progress-linear-bar { + height: $progress-linear-bar-height; + position: absolute; + width: 100%; + } + + .md-progress-linear-bar1, .md-progress-linear-bar2 { + transition: all 0.2s linear; + } + + &[md-mode="determinate"] { + .md-progress-linear-bar1 { + display: none; + } + } + + &[md-mode="indeterminate"] { + .md-progress-linear-bar1 { + animation: indeterminate1 4s infinite linear; + } + + .md-progress-linear-bar2 { + animation: indeterminate2 4s infinite linear; + } + } + + &[md-mode="buffer"] { + .md-progress-linear-container { + background-color: transparent !important; + } + + .md-progress-linear-dashed:before { + content: ""; + display: block; + height: $progress-linear-bar-height; + width: 100%; + margin-top: 0px; + position: absolute; + background-color: transparent; + background-size: 10px 10px !important; + background-position: 0px -23px; + animation: buffer 3s infinite linear; + } + } + + &[md-mode="query"] { + .md-progress-linear-bar2 { + animation: query .8s infinite cubic-bezier(0.390, 0.575, 0.565, 1.000); + } + } +} + +@keyframes indeterminate1 { + 0% { + transform: translateX(-25%) scale(.5, 1); + } + 10% { + transform: translateX(25%) scale(.5, 1); + } + 19.99% { + transform: translateX(50%) scale(0, 1); + } + 20% { + transform: translateX(-37.5%) scale(.25, 1); + } + 30% { + transform: translateX(37.5%) scale(.25, 1); + } + 34.99% { + transform: translateX(50%) scale(0, 1); + } + 36.99% { + transform: translateX(50%) scale(0, 1); + } + 37% { + transform: translateX(-37.5%) scale(.25, 1); + } + 47% { + transform: translateX(20%) scale(.25, 1); + } + 52% { + transform: translateX(35%) scale(.05, 1); + } + 55% { + transform: translateX(35%) scale(.1, 1); + } + 58% { + transform: translateX(50%) scale(.1, 1); + } + 61.99% { + transform: translateX(50%) scale(0, 1); + } + 69.99% { + transform: translateX(50%) scale(0, 1); + } + 70% { + transform: translateX(-37.5%) scale(.25, 1); + } + 80% { + transform: translateX(20%) scale(.25, 1); + } + 85% { + transform: translateX(35%) scale(.05, 1); + } + 88% { + transform: translateX(35%) scale(.1, 1); + } + 91% { + transform: translateX(50%) scale(.1, 1); + } + 92.99% { + transform: translateX(50%) scale(0, 1); + } + 93% { + transform: translateX(-50%) scale(0, 1); + } + 100% { + transform: translateX(-25%) scale(.5, 1); + } +} + +@keyframes indeterminate2 { + 0% { + transform: translateX(-50%) scale(0, 1); + } + 25.99%{ + transform: translateX(-50%) scale(0, 1); + } + 28% { + transform: translateX(-37.5%) scale(.25, 1); + } + 38% { + transform: translateX(37.5%) scale(.25, 1); + } + 42.99% { + transform: translateX(50%) scale(0, 1); + } + 46.99% { + transform: translateX(50%) scale(0, 1); + } + 49.99% { + transform: translateX(50%) scale(0, 1); + } + 50% { + transform: translateX(-50%) scale(0, 1); + } + 60% { + transform: translateX(-25%) scale(.5, 1); + } + 70% { + transform: translateX(25%) scale(.5, 1); + } + 79.99% { + transform: translateX(50%) scale(0, 1); + } +} + +@keyframes query { + 0% { + opacity: 1; + transform: translateX(35%) scale(.3, 1); + } + 100% { + opacity: 0; + transform: translateX(-50%) scale(0, 1); + } +} + +@keyframes buffer { + 0% { + opacity: 1; + background-position: 0px -23px; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + background-position: -200px -23px; + } +} + + +// THEME + +md-progress-linear { + .md-progress-linear-container { + background-color: md-color($md-primary, 100); + } + + .md-progress-linear-bar { + background-color: md-color($md-primary); + } + + &.md-warn .md-progress-linear-container { + background-color: md-color($md-warn, 100); + } + + &.md-warn .md-progress-linear-bar { + background-color: md-color($md-warn); + } + + &.md-accent .md-progress-linear-container { + background-color: md-color($md-accent, 100); + } + + &.md-accent .md-progress-linear-bar { + background-color: md-color($md-accent); + } + + &[md-mode=buffer] { + &.md-primary { + .md-progress-linear-bar1 { + background-color: md-color($md-primary, 100); + } + .md-progress-linear-dashed:before { + background: radial-gradient(md-color($md-primary, 100) 0%, md-color($md-primary, 100) 16%, transparent 42%); + } + } + &.md-warn { + .md-progress-linear-bar1 { + background-color: md-color($md-warn, 100); + } + .md-progress-linear-dashed:before { + background: radial-gradient(md-color($md-warn, 100) 0%, md-color($md-warn, 100) 16%, transparent 42%); + } + } + &.md-accent { + .md-progress-linear-bar1 { + background-color: md-color($md-accent, 100); + } + .md-progress-linear-dashed:before { + background: radial-gradient(md-color($md-accent, 100) 0%, md-color($md-accent, 100) 16%, transparent 42%); + } + } + } +} diff --git a/ng2-material/components/progress_linear/progress_linear.ts b/ng2-material/components/progress_linear/progress_linear.ts new file mode 100644 index 00000000..22390231 --- /dev/null +++ b/ng2-material/components/progress_linear/progress_linear.ts @@ -0,0 +1,90 @@ +import {Component, View, ViewEncapsulation, Attribute, OnChanges} from 'angular2/angular2'; +import {CONST} from 'angular2/src/facade/lang'; +import {isPresent, isBlank} from 'angular2/src/facade/lang'; +import {Math} from 'angular2/src/facade/math'; + +/** Different display / behavior modes for progress_linear. */ +@CONST() +class ProgressMode { + @CONST() static DETERMINATE = 'determinate'; + @CONST() static INDETERMINATE = 'indeterminate'; + @CONST() static BUFFER = 'buffer'; + @CONST() static QUERY = 'query'; +} + +@Component({ + selector: 'md-progress-linear', + inputs: ['value', 'bufferValue'], + host: { + 'role': 'progressbar', + 'aria-valuemin': '0', + 'aria-valuemax': '100', + '[attr.aria-valuenow]': 'value' + } +}) +@View({ + templateUrl: 'ng2-material/components/progress_linear/progress_linear.html', + directives: [], + encapsulation: ViewEncapsulation.None +}) +export class MdProgressLinear implements OnChanges { + /** Value for the primary bar. */ + value_: number; + + /** Value for the secondary bar. */ + bufferValue: number; + + /** The render mode for the progress bar. */ + mode: string; + + /** CSS `transform` property applied to the primary bar. */ + primaryBarTransform: string; + + /** CSS `transform` property applied to the secondary bar. */ + secondaryBarTransform: string; + + constructor(@Attribute('mode') mode: string) { + this.primaryBarTransform = ''; + this.secondaryBarTransform = ''; + + this.mode = isPresent(mode) ? mode : ProgressMode.DETERMINATE; + } + + get value() { + return this.value_; + } + + set value(v) { + if (isPresent(v)) { + this.value_ = MdProgressLinear.clamp(v); + } + } + + ngOnChanges(_) { + // If the mode does not use a value, or if there is no value, do nothing. + if (this.mode == ProgressMode.QUERY || this.mode == ProgressMode.INDETERMINATE || + isBlank(this.value)) { + return; + } + + this.primaryBarTransform = this.transformForValue(this.value); + + // The bufferValue is only used in buffer mode. + if (this.mode == ProgressMode.BUFFER) { + this.secondaryBarTransform = this.transformForValue(this.bufferValue); + } + } + + /** Gets the CSS `transform` property for a progress bar based on the given value (0 - 100). */ + transformForValue(value) { + // TODO(jelbourn): test perf gain of caching these, since there are only 101 values. + let scale = value / 100; + let translateX = (value - 100) / 2; + return `translateX(${translateX}%) scale(${scale}, 1)`; + } + + /** Clamps a value to be between 0 and 100. */ + static clamp(v) { + return Math.max(0, Math.min(100, v)); + } +} diff --git a/ng2-material/components/radio/radio_button.html b/ng2-material/components/radio/radio_button.html new file mode 100644 index 00000000..d16ba7ad --- /dev/null +++ b/ng2-material/components/radio/radio_button.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/ng2-material/components/radio/radio_button.scss b/ng2-material/components/radio/radio_button.scss new file mode 100644 index 00000000..d1bbe065 --- /dev/null +++ b/ng2-material/components/radio/radio_button.scss @@ -0,0 +1,141 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$radio-width: 16px !default; +$radio-height: $radio-width !default; + +md-radio-button { + display: block; + margin: 15px; + white-space: nowrap; + cursor: pointer; +} + +md-radio-group { + border: 1px dotted transparent; + display: inline-block; + outline: none; +} + +.md-radio-container { + box-sizing: border-box; + position: relative; + top: 4px; + display: inline-block; + width: $radio-width; + height: $radio-width; + cursor: pointer; +} + +.md-radio-off { + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + width: $radio-width; + height: $radio-width; + border: solid 2px; + border-radius: 50%; + transition: border-color ease 0.28s; +} + +.md-radio-on { + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + width: $radio-width; + height: $radio-width; + border-radius: 50%; + transition: transform ease 0.28s; + transform: scale(0); + + .md-radio-checked & { + transform: scale(0.55); + } +} + +// This is the style applied to the content (included via ). If we could rely on shadow +// DOM always being present, this would use the ::content psuedo-class. +.md-radio-label { + position: relative; + display: inline-block; + margin-left: 10px; + margin-right: 10px; + vertical-align: middle; + white-space: normal; + // pointer-events: none; ??? + width: auto; +} + +.md-radio-root { + display: block; + cursor: pointer; +} + + +// THEME + +.md-radio-off { + border-color: md-color($md-foreground, icon); +} + +.md-radio-on { + background-color: md-color($md-accent, 0.87); +} + +.md-radio-checked .md-radio-off { + border-color: md-color($md-accent, 0.87); +} + +// TODO +.md-radio-checked .md-ink-ripple { } +.md-radio-container .md-ripple { } +p + +md-radio-group:not([disabled]) md-radio:not([disabled]) { + + &.md-primary { + .md-radio-on { + background-color: md-color($md-primary, 0.87); + } + + &.md-radio-checked .md-radio-off { + border-color: md-color($md-primary, 0.87); + } + + &.md-radio-checked .md-ink-ripple { + color: md-color($md-primary, 0.87); + } + + .md-radio-container .md-ripple { + color: md-color($md-primary, 600); + } + } + + &.md-warn { + .md-radio-on { + background-color: md-color($md-warn, 0.87); + } + &.md-radio-checked .md-radio-off { + border-color: md-color($md-warn, 0.87); + } + &.md-radio-checked .md-ink-ripple { + color: md-color($md-warn, 0.87); + } + .md-radio-container .md-ripple { + color: md-color($md-warn, 600); + } + } +} + +md-radio-button[disabled], +md-radio-group[disabled] md-radio-button { + .md-radio-container .md-radio-off, + .md-radio-container .md-radio-on { + border-color: md-color($md-foreground, disabled); + } +} diff --git a/ng2-material/components/radio/radio_button.ts b/ng2-material/components/radio/radio_button.ts new file mode 100644 index 00000000..15898890 --- /dev/null +++ b/ng2-material/components/radio/radio_button.ts @@ -0,0 +1,313 @@ +import { + Component, + View, + ViewEncapsulation, + Host, + SkipSelf, + Attribute, + Optional, + OnChanges, + OnInit +} from 'angular2/angular2'; + +import {isPresent, StringWrapper, NumberWrapper} from 'angular2/src/facade/lang'; +import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; +import {Event, KeyboardEvent} from 'angular2/src/facade/browser'; + +import {MdRadioDispatcher} from './radio_dispatcher'; +import {KeyCodes} from '../../core/key_codes'; + +// TODO(jelbourn): Behaviors to test +// Disabled radio don't select +// Disabled radios don't propagate click event +// Radios are disabled by parent group +// Radios set default tab index iff not in parent group +// Radios are unique-select +// Radio updates parent group's value +// Change to parent group's value updates the selected child radio +// Radio name is pulled on parent group +// Radio group changes on arrow keys +// Radio group skips disabled radios on arrow keys + +var _uniqueIdCounter: number = 0; + +@Component({ + selector: 'md-radio-group', + outputs: ['change'], + inputs: ['disabled', 'value'], + host: { + 'role': 'radiogroup', + '[attr.aria-disabled]': 'disabled', + '[attr.aria-activedescendant]': 'activedescendant', + // TODO(jelbourn): Remove ^ when event retargeting is fixed. + '(keydown)': 'onKeydown($event)', + '[tabindex]': 'tabindex', + } +}) +@View({ + templateUrl: 'ng2-material/components/radio/radio_group.html', + encapsulation: ViewEncapsulation.None +}) +export class MdRadioGroup implements OnChanges { + /** The selected value for the radio group. The value comes from the options. */ + value: any; + + /** The HTML name attribute applied to radio buttons in this group. */ + name_: string; + + /** Dispatcher for coordinating radio unique-selection by name. */ + radioDispatcher: MdRadioDispatcher; + + /** Array of child radio buttons. */ + radios_: MdRadioButton[]; + + activedescendant: any; + + disabled_: boolean; + + /** The ID of the selected radio button. */ + selectedRadioId: string; + + change: EventEmitter; + + tabindex: number; + + constructor(@Attribute('tabindex') tabindex: string, @Attribute('disabled') disabled: string, + radioDispatcher: MdRadioDispatcher) { + this.name_ = `md-radio-group-${_uniqueIdCounter++}`; + this.radios_ = []; + this.change = new EventEmitter(); + this.radioDispatcher = radioDispatcher; + this.selectedRadioId = ''; + this.disabled_ = false; + + // The simple presence of the `disabled` attribute dictates disabled state. + this.disabled = isPresent(disabled); + + // If the user has not set a tabindex, default to zero (in the normal document flow). + this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0; + } + + /** Gets the name of this group, as to be applied in the HTML 'name' attribute. */ + getName(): string { + return this.name_; + } + + get disabled() { + return this.disabled_; + } + + set disabled(value) { + this.disabled_ = isPresent(value) && value !== false; + } + + /** Change handler invoked when bindings are resolved or when bindings have changed. */ + ngOnChanges(_) { + // If the component has a disabled attribute with no value, it will set disabled = ''. + this.disabled = isPresent(this.disabled) && this.disabled !== false; + + // If the value of this radio-group has been set or changed, we have to look through the + // child radio buttons and select the one that has a corresponding value (if any). + if (isPresent(this.value) && this.value != '') { + this.radioDispatcher.notify(this.name_); + this.radios_.forEach(radio => { + if (radio.value == this.value) { + radio.checked = true; + this.selectedRadioId = radio.id; + this.activedescendant = radio.id; + } + }); + } + } + + /** Update the value of this radio group from a child md-radio being selected. */ + updateValue(value: any, id: string) { + this.value = value; + this.selectedRadioId = id; + this.activedescendant = id; + ObservableWrapper.callEmit(this.change, null); + } + + /** Registers a child radio button with this group. */ + register(radio: MdRadioButton) { + this.radios_.push(radio); + } + + /** Handles up and down arrow key presses to change the selected child radio. */ + onKeydown(event: KeyboardEvent) { + if (this.disabled) { + return; + } + + switch (event.keyCode) { + case KeyCodes.UP: + this.stepSelectedRadio(-1); + event.preventDefault(); + break; + case KeyCodes.DOWN: + this.stepSelectedRadio(1); + event.preventDefault(); + break; + } + } + + // TODO(jelbourn): Replace this with a findIndex method in the collections facade. + getSelectedRadioIndex(): number { + for (let i = 0; i < this.radios_.length; i++) { + if (this.radios_[i].id == this.selectedRadioId) { + return i; + } + } + + return -1; + } + + /** Steps the selected radio based on the given step value (usually either +1 or -1). */ + stepSelectedRadio(step) { + let index = this.getSelectedRadioIndex() + step; + if (index < 0 || index >= this.radios_.length) { + return; + } + + let radio = this.radios_[index]; + + // If the next radio is line is disabled, skip it (maintaining direction). + if (radio.disabled) { + this.stepSelectedRadio(step + (step < 0 ? -1 : 1)); + return; + } + + this.radioDispatcher.notify(this.name_); + radio.checked = true; + ObservableWrapper.callEmit(this.change, null); + + this.value = radio.value; + this.selectedRadioId = radio.id; + this.activedescendant = radio.id; + } +} + + +@Component({ + selector: 'md-radio-button', + inputs: ['id', 'name', 'value', 'checked', 'disabled'], + host: { + 'role': 'radio', + '[id]': 'id', + '[tabindex]': 'tabindex', + '[attr.aria-checked]': 'checked', + '[attr.aria-disabled]': 'disabled', + '(keydown)': 'onKeydown($event)', + } +}) +@View({ + templateUrl: 'ng2-material/components/radio/radio_button.html', + directives: [], + encapsulation: ViewEncapsulation.None +}) +export class MdRadioButton implements OnInit { + /** Whether this radio is checked. */ + checked: boolean; + + /** Whether the radio is disabled. */ + disabled_: boolean; + + /** The unique ID for the radio button. */ + id: string; + + /** Analog to HTML 'name' attribute used to group radios for unique selection. */ + name: string; + + /** Value assigned to this radio. Used to assign the value to the parent MdRadioGroup. */ + value: any; + + /** The parent radio group. May or may not be present. */ + radioGroup: MdRadioGroup; + + /** Dispatcher for coordinating radio unique-selection by name. */ + radioDispatcher: MdRadioDispatcher; + + tabindex: number; + + constructor(@Optional() @SkipSelf() @Host() radioGroup: MdRadioGroup, @Attribute('id') id: string, + @Attribute('tabindex') tabindex: string, radioDispatcher: MdRadioDispatcher) { + // Assertions. Ideally these should be stripped out by the compiler. + // TODO(jelbourn): Assert that there's no name binding AND a parent radio group. + + this.radioGroup = radioGroup; + this.radioDispatcher = radioDispatcher; + this.value = null; + this.checked = false; + + this.id = isPresent(id) ? id : `md-radio-${_uniqueIdCounter++}`; + + // Whenever a radio button with the same name is checked, uncheck this radio button. + radioDispatcher.listen((name) => { + if (name == this.name) { + this.checked = false; + } + }); + + // When this radio-button is inside of a radio-group, the group determines the name. + if (isPresent(radioGroup)) { + this.name = radioGroup.getName(); + this.radioGroup.register(this); + } + + // If the user has not set a tabindex, default to zero (in the normal document flow). + if (!isPresent(radioGroup)) { + this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0; + } else { + this.tabindex = -1; + } + } + + /** Change handler invoked when bindings are resolved or when bindings have changed. */ + ngOnInit() { + if (isPresent(this.radioGroup)) { + this.name = this.radioGroup.getName(); + } + } + + /** Whether this radio button is disabled, taking the parent group into account. */ + isDisabled(): boolean { + // Here, this.disabled may be true/false as the result of a binding, may be the empty string + // if the user just adds a `disabled` attribute with no value, or may be absent completely. + // TODO(jelbourn): If someone sets `disabled="disabled"`, will this work in dart? + return this.disabled || (isPresent(this.disabled) && StringWrapper.equals(this.disabled, '')) || + (isPresent(this.radioGroup) && this.radioGroup.disabled); + } + + get disabled(): any { + return this.disabled_; + } + + set disabled(value: any) { + this.disabled_ = isPresent(value) && value !== false; + } + + /** Select this radio button. */ + select(event: Event) { + if (this.isDisabled()) { + event.stopPropagation(); + return; + } + + // Notifiy all radio buttons with the same name to un-check. + this.radioDispatcher.notify(this.name); + + this.checked = true; + + if (isPresent(this.radioGroup)) { + this.radioGroup.updateValue(this.value, this.id); + } + } + + /** Handles pressing the space key to select this focused radio button. */ + onKeydown(event: KeyboardEvent) { + if (event.keyCode == KeyCodes.SPACE) { + event.preventDefault(); + this.select(event); + } + } +} diff --git a/ng2-material/components/radio/radio_dispatcher.ts b/ng2-material/components/radio/radio_dispatcher.ts new file mode 100644 index 00000000..ca718001 --- /dev/null +++ b/ng2-material/components/radio/radio_dispatcher.ts @@ -0,0 +1,25 @@ +import {Injectable} from 'angular2/angular2'; + +/** + * Class for radio buttons to coordinate unique selection based on name. + * Indended to be consumed as an Angular service. + */ +@Injectable() +export class MdRadioDispatcher { + // TODO(jelbourn): Change this to TypeScript syntax when supported. + listeners_: Function[]; + + constructor() { + this.listeners_ = []; + } + + /** Notify other nadio buttons that selection for the given name has been set. */ + notify(name: string) { + this.listeners_.forEach(listener => listener(name)); + } + + /** Listen for future changes to radio button selection. */ + listen(listener) { + this.listeners_.push(listener); + } +} diff --git a/ng2-material/components/radio/radio_group.html b/ng2-material/components/radio/radio_group.html new file mode 100644 index 00000000..6dbc7430 --- /dev/null +++ b/ng2-material/components/radio/radio_group.html @@ -0,0 +1 @@ + diff --git a/ng2-material/components/radio/radio_group.scss b/ng2-material/components/radio/radio_group.scss new file mode 100644 index 00000000..9740f0f0 --- /dev/null +++ b/ng2-material/components/radio/radio_group.scss @@ -0,0 +1,5 @@ +md-radio-group { + border: 1px dotted transparent; + display: block; + outline: none; +} diff --git a/ng2-material/components/sidenav/sidenav.scss b/ng2-material/components/sidenav/sidenav.scss new file mode 100644 index 00000000..2dca0037 --- /dev/null +++ b/ng2-material/components/sidenav/sidenav.scss @@ -0,0 +1,125 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; +@import "../../core/style/default-theme"; + +$sidenav-mobile-width: 320px !default; +$sidenav-desktop-width: 400px !default; +$sidenav-min-space: 56px !default; + +md-sidenav { + box-sizing: border-box; + position: absolute; + flex-direction: column; + z-index: $z-index-sidenav; + + width: $sidenav-mobile-width; + max-width: $sidenav-mobile-width; + bottom: 0; + overflow: auto; + + ul { + list-style: none; + } + + &.md-closed { + display: none; + } + &.md-closed-add, + &.md-closed-remove { + display: flex; + transition: 0.2s ease-in all; + } + + &.md-closed-add.md-closed-add-active, + &.md-closed-remove.md-closed-remove-active { + transition: $swift-ease-out; + } + + &.md-locked-open-add, + &.md-locked-open-remove { + position: static; + display: flex; + transform: translate3d(0, 0, 0); + } + + &.md-locked-open, + &.md-locked-open.md-closed, + &.md-locked-open.md-closed.md-sidenav-left, + &.md-locked-open.md-closed.md-sidenav-right, + &.md-locked-open-remove.md-closed { + position: static; + display: flex; + transform: translate3d(0, 0, 0); + } + &.md-locked-open-remove-active { + transition: width $swift-ease-in-duration $swift-ease-in-timing-function, + min-width $swift-ease-in-duration $swift-ease-in-timing-function; + width: 0; + min-width: 0; + } + + &.md-closed.md-locked-open-add { + width: 0; + min-width: 0; + transform: translate3d(0%, 0, 0); + } + + &.md-closed.md-locked-open-add-active { + transition: width $swift-ease-in-duration $swift-ease-in-timing-function, + min-width $swift-ease-in-duration $swift-ease-in-timing-function; + width: $sidenav-mobile-width; + min-width: $sidenav-mobile-width; + transform: translate3d(0%, 0, 0); + } + + @extend .md-sidenav-left; +} +.md-sidenav-backdrop.md-locked-open { + display: none; +} + +.md-sidenav-left { + left: 0; + top: 0; + transform: translate3d(0%, 0, 0); + &.md-closed { + transform: translate3d(-100%, 0, 0); + } +} + +.md-sidenav-right { + left: 100%; + top: 0; + transform: translate3d(-100%, 0, 0); + &.md-closed { + transform: translate3d(0%, 0, 0); + } +} + +@media screen and (min-width: $layout-breakpoint-sm) { + md-sidenav { + max-width: $sidenav-desktop-width; + } +} + +@media screen and (max-width: $sidenav-desktop-width + $sidenav-min-space) { + md-sidenav { + width: calc(100% - #{$sidenav-min-space}); + min-width: calc(100% - #{$sidenav-min-space}); + max-width: calc(100% - #{$sidenav-min-space}); + } +} + +@media screen and (-ms-high-contrast: active) { + .md-sidenav-left { + border-right: 1px solid #fff; + } + .md-sidenav-right { + border-left: 1px solid #fff; + } +} + + +md-sidenav { + background-color: md-color($md-background,500); //'{{background-color}}'; +} diff --git a/ng2-material/components/sidenav/sidenav.ts b/ng2-material/components/sidenav/sidenav.ts new file mode 100644 index 00000000..6ca3cf66 --- /dev/null +++ b/ng2-material/components/sidenav/sidenav.ts @@ -0,0 +1,7 @@ +import {Directive} from 'angular2/core'; +import {Input} from 'angular2/core'; + +@Directive({selector: 'md-sidenav'}) +export class MdSidenav { + @Input() public opened: boolean = true; +} diff --git a/ng2-material/components/switcher/switch.html b/ng2-material/components/switcher/switch.html new file mode 100644 index 00000000..a05d61d3 --- /dev/null +++ b/ng2-material/components/switcher/switch.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ng2-material/components/switcher/switch.scss b/ng2-material/components/switcher/switch.scss new file mode 100644 index 00000000..3fb12f7d --- /dev/null +++ b/ng2-material/components/switcher/switch.scss @@ -0,0 +1,172 @@ +@import "../../core/style/variables"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$switch-width: 36px !default; +$switch-height: $baseline-grid * 3 !default; +$switch-bar-height: 14px !default; +$switch-thumb-size: 20px !default; + +md-switch { + display: flex; + align-items: center; + + margin: 15px; + white-space: nowrap; + cursor: pointer; + outline: none; + user-select: none; + + * { + box-sizing: border-box; + } + + .md-switch-container { + cursor: grab; + width: $switch-width; + height: $switch-height; + position: relative; + user-select: none; + margin-right: 8px; + } + + // If the user moves his mouse off the switch, stil display grabbing cursor + &:not([disabled]) { + .md-switch-dragging, + &.md-switch-dragging .md-switch-container { + cursor: grabbing; + } + } + + .md-switch-label { + border: 0 transparent; + } + + .md-switch-bar { + left: 1px; + width: $switch-width - 2px; + top: $switch-height / 2 - $switch-bar-height / 2; + height: $switch-bar-height; + border-radius: 8px; + position: absolute; + } + + .md-switch-thumb-container { + top: $switch-height / 2 - $switch-thumb-size / 2; + left: 0; + width: $switch-width - $switch-thumb-size; + position: absolute; + transform: translate3d(0,0,0); + z-index: 1; + } + + &[aria-checked="true"] .md-switch-thumb-container { + transform: translate3d(100%, 0, 0); + } + + .md-switch-thumb { + position: absolute; + margin: 0; + left: 0; + top: 0; + outline: none; + height: $switch-thumb-size; + width: $switch-thumb-size; + border-radius: 50%; + box-shadow: $whiteframe-shadow-z1; + + // todo + .md-ripple-container { + position: absolute; + display: block; + width: auto; + height: auto; + left: -$switch-thumb-size; + top: -$switch-thumb-size; + right: -$switch-thumb-size; + bottom: -$switch-thumb-size; + } + } + + &:not(.md-switch-dragging) { + .md-switch-bar, + .md-switch-thumb-container, + .md-switch-thumb { + transition: $swift-ease-in-out; + transition-property: transform, background-color; + } + .md-switch-bar, + .md-switch-thumb { + transition-delay: 0.05s; + } + } + +} + +// TODO(jelbourn): Why are these not defined in terms of the theme? +@media screen and (-ms-high-contrast: active) { + md-switch .md-switch-bar { + background-color: #666; + } + md-switch[aria-checked="true"] .md-switch-bar { + background-color: #9E9E9E; + } + md-switch.md-default-theme .md-switch-thumb { + background-color: #fff; + } +} + + +// THEME + +md-switch { + .md-switch-thumb { + background-color: md-color($md-background, 50); + } + .md-switch-bar { + background-color: md-color($md-background, 500); + } + + &[aria-checked="true"] { + .md-switch-thumb { + background-color: md-color($md-accent); + } + .md-switch-bar { + background-color: md-color($md-accent, 0.5); + } + + &.md-primary { + .md-switch-thumb { + background-color: md-color($md-primary); + } + .md-switch-bar { + background-color: md-color($md-primary, 0.5); + } + } + + &.md-warn { + .md-switch-thumb { + background-color: md-color($md-warn); + } + .md-switch-bar { + background-color: md-color($md-warn, 0.5); + } + } + } + + &[disabled] { + .md-switch-thumb { + background-color: md-color($md-background, 400); + } + .md-switch-bar { + background-color: md-color($md-foreground, divider); + } + } + + &:focus { + .md-switch-label:not(:empty) { + border: 1px dotted md-color($md-foreground, text); + } + } +} diff --git a/ng2-material/components/switcher/switch.ts b/ng2-material/components/switcher/switch.ts new file mode 100644 index 00000000..c879414c --- /dev/null +++ b/ng2-material/components/switcher/switch.ts @@ -0,0 +1,26 @@ +import {Component, View, ViewEncapsulation, Attribute} from 'angular2/angular2'; +import {MdCheckbox} from "../checkbox/checkbox"; + +// TODO(jelbourn): add gesture support +// TODO(jelbourn): clean up CSS. + +@Component({ + selector: 'md-switch', + inputs: ['checked', 'disabled'], + host: { + 'role': 'checkbox', + '[attr.aria-checked]': 'checked', + '[attr.aria-disabled]': 'disabled_', + '(keydown)': 'onKeydown($event)', + } +}) +@View({ + templateUrl: 'ng2-material/components/switcher/switch.html', + directives: [], + encapsulation: ViewEncapsulation.None +}) +export class MdSwitch extends MdCheckbox { + constructor(@Attribute('tabindex') tabindex: string) { + super(tabindex); + } +} diff --git a/ng2-material/components/toolbar/toolbar.scss b/ng2-material/components/toolbar/toolbar.scss new file mode 100644 index 00000000..eed25b9f --- /dev/null +++ b/ng2-material/components/toolbar/toolbar.scss @@ -0,0 +1,184 @@ +// Standard/Desktop Heights +$toolbar-tools-height: 64px !default; +$toolbar-height: 64px !default; +$toolbar-medium-tall-height: 88px !default; +$toolbar-tall-height: 128px !default; + +// Mobile portrait heights +$toolbar-tools-height-mobile-portrait: 56px !default; +$toolbar-height-mobile-portrait: 56px !default; + +// Mobile landscape heights +$toolbar-tools-height-mobile-landscape: 48px !default; +$toolbar-height-mobile-landscape: 48px !default; + + +$toolbar-indent-margin: 64px !default; +$toolbar-padding: 16px !default; + +$icon-button-margin-offset: rem(-0.800) !default; + +md-toolbar { + box-sizing: border-box; + display: flex; + flex-direction: column; + + position: relative; + z-index: 2; + + font-size: rem(2.0); + min-height: $baseline-grid * 8; + width: 100%; + + &.md-whiteframe-z1-add, &.md-whiteframe-z1-remove { + transition: box-shadow $swift-ease-in-out-duration linear; + } + + md-toolbar-filler { + width: 9 * $baseline-grid; + } + + *, + *:before, + *:after { + box-sizing: border-box; + } + + &.md-tall { + height: $toolbar-tall-height; + min-height: $toolbar-tall-height; + max-height: $toolbar-tall-height; + } + + &.md-medium-tall { + height: $toolbar-medium-tall-height; + min-height: $toolbar-medium-tall-height; + max-height: $toolbar-medium-tall-height; + + .md-toolbar-tools { + height: 48px; + min-height: 48px; + max-height: 48px; + } + } + + > .md-indent { + margin-left: $toolbar-indent-margin; + } + + ~ md-content { + > md-list { + padding: 0; + + md-list-item:last-child { + md-divider { + display: none; + } + } + } + } +} + +.md-toolbar-tools { + font-size: $title-font-size-base; + letter-spacing: 0.005em; + box-sizing: border-box; + font-weight: 400; + display: flex; + align-items: center; + flex-direction: row; + + width: 100%; + height: $toolbar-tools-height; + max-height: $toolbar-tools-height; + padding: 0 $toolbar-padding; + margin: 0; + + h1, h2, h3 { + font-size: inherit; + font-weight: inherit; + margin: inherit; + } + + a { + color: inherit; + text-decoration: none; + } + .fill-height { + display: flex; + align-items: center; + } + [md-button] { + margin-top: 0; + margin-bottom: 0; + } + &> [md-button]:first-child { + margin-left: $icon-button-margin-offset; + } + &> [md-button]:last-child { + margin-right: $icon-button-margin-offset; + } + + &> md-menu:last-child { + margin-right: $icon-button-margin-offset; + & > [md-button] { + margin-right: 0; + } + } + + @media screen and (-ms-high-contrast: active) { + border-bottom: 1px solid #fff; + } +} + +// Handle mobile portrait +@media only screen and (min-width: 0) and (max-width: $layout-breakpoint-sm) and + (orientation: portrait) { + md-toolbar { + min-height: $toolbar-height-mobile-portrait; + } + + .md-toolbar-tools { + height: $toolbar-height-mobile-portrait; + max-height: $toolbar-height-mobile-portrait; + } +} + +// Handle mobile landscape +@media only screen and (min-width: 0) and (max-width: $layout-breakpoint-sm) and + (orientation: landscape) { + md-toolbar { + min-height: $toolbar-height-mobile-landscape; + } + + .md-toolbar-tools { + height: $toolbar-height-mobile-landscape; + max-height: $toolbar-height-mobile-landscape; + } +} + +// +// Theme +// + +md-toolbar:not(.md-menu-toolbar) { + background-color: md-color($md-primary); //'{{primary-color}}'; + color: md-color($md-primary, default-contrast); //'{{primary-contrast}}'; + + md-icon { + color: md-color($md-primary, default-contrast); //'{{primary-contrast}}'; + } + + [md-button] { + color: md-color($md-primary, default-contrast); //'{{primary-contrast}}'; + } + + &.md-accent { + background-color: md-color($md-accent); //'{{accent-color}}'; + color: md-color($md-accent, default-contrast); //'{{accent-contrast}}'; + } + &.md-warn { + background-color: md-color($md-warn); //'{{warn-color}}'; + color: md-color($md-warn, default-contrast); //'{{warn-contrast}}'; + } +} diff --git a/ng2-material/components/toolbar/toolbar.ts b/ng2-material/components/toolbar/toolbar.ts new file mode 100644 index 00000000..758de84b --- /dev/null +++ b/ng2-material/components/toolbar/toolbar.ts @@ -0,0 +1,228 @@ +import {View,Component} from 'angular2/core'; +import {Directive} from 'angular2/core'; + +@Directive({selector: 'md-toolbar'}) +export class MdToolbar { +} + + +///** +// * @ngdoc directive +// * @name mdToolbar +// * @module material.components.toolbar +// * @restrict E +// * @description +// * `md-toolbar` is used to place a toolbar in your app. +// * +// * Toolbars are usually used above a content area to display the title of the +// * current page, and show relevant action buttons for that page. +// * +// * You can change the height of the toolbar by adding either the +// * `md-medium-tall` or `md-tall` class to the toolbar. +// * +// * @usage +// * +// * +// * +// * +// * +// * My App's Title +// * +// * +// * +// * +// * +// * Right Bar Button +// * +// * +// * +// * +// * +// * Hello! +// * +// * +// * +// * +// * @param {boolean=} md-scroll-shrink Whether the header should shrink away as +// * the user scrolls down, and reveal itself as the user scrolls up. +// * +// * _**Note (1):** for scrollShrink to work, the toolbar must be a sibling of a +// * `md-content` element, placed before it. See the scroll shrink demo._ +// * +// * _**Note (2):** The `md-scroll-shrink` attribute is only parsed on component +// * initialization, it does not watch for scope changes._ +// * +// * +// * @param {number=} md-shrink-speed-factor How much to change the speed of the toolbar's +// * shrinking by. For example, if 0.25 is given then the toolbar will shrink +// * at one fourth the rate at which the user scrolls down. Default 0.5. +// */ +// +//function mdToolbarDirective($$rAF, $mdConstant, $mdUtil, $mdTheming, $animate) { +// var translateY = angular.bind(null, $mdUtil.supplant, 'translate3d(0,{0}px,0)'); +// +// return { +// restrict: 'E', +// +// link: function(scope, element, attr) { +// +// $mdTheming(element); +// +// if (angular.isDefined(attr.mdScrollShrink)) { +// setupScrollShrink(); +// } +// +// function setupScrollShrink() { +// +// var toolbarHeight; +// var contentElement; +// var disableScrollShrink = angular.noop; +// +// // Current "y" position of scroll +// // Store the last scroll top position +// var y = 0; +// var prevScrollTop = 0; +// var shrinkSpeedFactor = attr.mdShrinkSpeedFactor || 0.5; +// +// var debouncedContentScroll = $$rAF.throttle(onContentScroll); +// var debouncedUpdateHeight = $mdUtil.debounce(updateToolbarHeight, 5 * 1000); +// +// // Wait for $mdContentLoaded event from mdContent directive. +// // If the mdContent element is a sibling of our toolbar, hook it up +// // to scroll events. +// +// scope.$on('$mdContentLoaded', onMdContentLoad); +// +// // If the toolbar is used inside an ng-if statement, we may miss the +// // $mdContentLoaded event, so we attempt to fake it if we have a +// // md-content close enough. +// +// attr.$observe('mdScrollShrink', onChangeScrollShrink); +// +// // If the toolbar has ngShow or ngHide we need to update height immediately as it changed +// // and not wait for $mdUtil.debounce to happen +// +// if (attr.ngShow) { scope.$watch(attr.ngShow, updateToolbarHeight); } +// if (attr.ngHide) { scope.$watch(attr.ngHide, updateToolbarHeight); } +// +// // If the scope is destroyed (which could happen with ng-if), make sure +// // to disable scroll shrinking again +// +// scope.$on('$destroy', disableScrollShrink); +// +// /** +// * +// */ +// function onChangeScrollShrink(shrinkWithScroll) { +// var closestContent = element.parent().find('md-content'); +// +// // If we have a content element, fake the call; this might still fail +// // if the content element isn't a sibling of the toolbar +// +// if (!contentElement && closestContent.length) { +// onMdContentLoad(null, closestContent); +// } +// +// // Evaluate the expression +// shrinkWithScroll = scope.$eval(shrinkWithScroll); +// +// // Disable only if the attribute's expression evaluates to false +// if (shrinkWithScroll === false) { +// disableScrollShrink(); +// } else { +// disableScrollShrink = enableScrollShrink(); +// } +// } +// +// /** +// * +// */ +// function onMdContentLoad($event, newContentEl) { +// // Toolbar and content must be siblings +// if (newContentEl && element.parent()[0] === newContentEl.parent()[0]) { +// // unhook old content event listener if exists +// if (contentElement) { +// contentElement.off('scroll', debouncedContentScroll); +// } +// +// contentElement = newContentEl; +// disableScrollShrink = enableScrollShrink(); +// } +// } +// +// /** +// * +// */ +// function onContentScroll(e) { +// var scrollTop = e ? e.target.scrollTop : prevScrollTop; +// +// debouncedUpdateHeight(); +// +// y = Math.min( +// toolbarHeight / shrinkSpeedFactor, +// Math.max(0, y + scrollTop - prevScrollTop) +// ); +// +// element.css($mdConstant.CSS.TRANSFORM, translateY([-y * shrinkSpeedFactor])); +// contentElement.css($mdConstant.CSS.TRANSFORM, translateY([(toolbarHeight - y) * shrinkSpeedFactor])); +// +// prevScrollTop = scrollTop; +// +// $mdUtil.nextTick(function() { +// var hasWhiteFrame = element.hasClass('md-whiteframe-z1'); +// +// if (hasWhiteFrame && !y) { +// $animate.removeClass(element, 'md-whiteframe-z1'); +// } else if (!hasWhiteFrame && y) { +// $animate.addClass(element, 'md-whiteframe-z1'); +// } +// }); +// +// } +// +// /** +// * +// */ +// function enableScrollShrink() { +// if (!contentElement) return angular.noop; // no md-content +// +// contentElement.on('scroll', debouncedContentScroll); +// contentElement.attr('scroll-shrink', 'true'); +// +// $$rAF(updateToolbarHeight); +// +// return function disableScrollShrink() { +// contentElement.off('scroll', debouncedContentScroll); +// contentElement.attr('scroll-shrink', 'false'); +// +// $$rAF(updateToolbarHeight); +// } +// } +// +// /** +// * +// */ +// function updateToolbarHeight() { +// toolbarHeight = element.prop('offsetHeight'); +// // Add a negative margin-top the size of the toolbar to the content el. +// // The content will start transformed down the toolbarHeight amount, +// // so everything looks normal. +// // +// // As the user scrolls down, the content will be transformed up slowly +// // to put the content underneath where the toolbar was. +// var margin = (-toolbarHeight * shrinkSpeedFactor) + 'px'; +// +// contentElement.css({ +// "margin-top": margin, +// "margin-bottom": margin +// }); +// +// onContentScroll(); +// } +// +// } +// +// } +// }; +// +//} diff --git a/ng2-material/core/key_codes.ts b/ng2-material/core/key_codes.ts new file mode 100644 index 00000000..ae7f080c --- /dev/null +++ b/ng2-material/core/key_codes.ts @@ -0,0 +1,10 @@ +import {CONST} from 'angular2/src/facade/lang'; + +// Can't use an enum because Dart doesn't support enum initializers. +@CONST() +export class KeyCodes { + @CONST() static ESCAPE = 27; + @CONST() static SPACE = 32; + @CONST() static UP = 38; + @CONST() static DOWN = 40; +} diff --git a/ng2-material/core/style/default-theme.scss b/ng2-material/core/style/default-theme.scss new file mode 100644 index 00000000..03d0e435 --- /dev/null +++ b/ng2-material/core/style/default-theme.scss @@ -0,0 +1,13 @@ +@import 'theme-functions'; +@import 'palette'; + + +// Person creating a theme writes variables like this: +$md-is-dark-theme: false; + + +$md-primary: md-palette($md-indigo, 500, 100, 700, $md-contrast-palettes); +$md-accent: md-palette($md-yellow, 800, 900, 400, $md-contrast-palettes); +$md-background: md-palette($md-grey, 500, 300, 600, $md-contrast-palettes); +$md-warn: md-palette($md-red, 500, 300, 800, $md-contrast-palettes); +$md-foreground: if($md-is-dark-theme, $md-dark-theme-foreground, $md-light-theme-foreground); diff --git a/ng2-material/core/style/layout.scss b/ng2-material/core/style/layout.scss new file mode 100644 index 00000000..3c96d524 --- /dev/null +++ b/ng2-material/core/style/layout.scss @@ -0,0 +1,366 @@ +// Responsive attributes +// ------------------------------ + +// hide means hide everywhere +/* Sizes: + 0 <= size < 600 Phone + 600 <= size < 960 Tablet + 960 <= size < 1200 Tablet-Landscape + 1200 <= size PC +*/ + +[layout] { + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -moz-flex; + display: -ms-flexbox; + display: flex; +} + +[layout=column] { + flex-direction: column; +} + +[layout=row] { + flex-direction: row; +} + +[layout-padding] > [flex-sm], +[layout-padding] > [flex-lt-md] +{ + padding: $layout-gutter-width / 4; +} +[layout-padding], +[layout-padding] > [flex], +[layout-padding] > [flex-gt-sm], +[layout-padding] > [flex-md], +[layout-padding] > [flex-lt-lg] +{ + padding: $layout-gutter-width / 2; +} +[layout-padding] > [flex-gt-md], +[layout-padding] > [flex-lg] +{ + padding: $layout-gutter-width / 1; +} + + +[layout-margin] > [flex-sm], +[layout-margin] > [flex-lt-md] +{ + margin: $layout-gutter-width / 4; +} +[layout-margin], +[layout-margin] > [flex], +[layout-margin] > [flex-gt-sm], +[layout-margin] > [flex-md], +[layout-margin] > [flex-lt-lg] { + margin: $layout-gutter-width / 2; +} +[layout-margin] > [flex-gt-md], +[layout-margin] > [flex-lg] +{ + margin: $layout-gutter-width / 1; +} + + + +[layout-wrap] { + flex-wrap: wrap; +} + +[layout-fill] { + margin: 0; + min-height: 100%; + width: 100%; +} +@-moz-document url-prefix() { + [layout-fill] { + margin: 0; + width: 100%; + min-height: auto; + height: inherit; + } +} + +@mixin flex-order-for-name($suffix: null) { + $selector: 'flex-order'; + @if $suffix != null { + $selector: 'flex-order-#{$suffix}'; + } + [#{$selector}="0"] { order: 0; } + [#{$selector}="1"] { order: 1; } + [#{$selector}="2"] { order: 2; } + [#{$selector}="3"] { order: 3; } + [#{$selector}="4"] { order: 4; } + [#{$selector}="5"] { order: 5; } + [#{$selector}="6"] { order: 6; } + [#{$selector}="7"] { order: 7; } + [#{$selector}="8"] { order: 8; } + [#{$selector}="9"] { order: 9; } +} + +@mixin layout-for-name($name) { + [layout-#{$name}] { + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + } + [layout-#{$name}=column] { + flex-direction: column; + } + [layout-#{$name}=row] { + flex-direction: row; + } +} + +@mixin offset-for-name($name: null) { + $offsetName: 'offset'; + @if $name != null { + $offsetName: 'offset-#{$name}'; + } + @for $i from 1 through 19 { + [#{$offsetName}="#{$i * 5}"] { + margin-left: #{$i * 5 + '%'}; + } + } + [#{$offsetName}="33"], [#{$offsetName}="34"] { + margin-left: 33.33%; + } + [#{$offsetName}="66"], [#{$offsetName}="67"] { + margin-left: 66.66%; + } +} + +@mixin flex-properties-for-name($name: null) { + $flexName: 'flex'; + @if $name != null { + $flexName: 'flex-#{$name}'; + } + + [#{$flexName}] { + box-sizing: border-box; + flex: 1; + } + + + // (0-20) * 5 = 0-100% + @for $i from 0 through 20 { + [#{$flexName}="#{$i * 5}"] { + flex: 0 0 #{$i * 5 + '%'}; + } + [layout="row"] > [#{$flexName}="#{$i * 5}"] { + max-width: #{$i * 5 + '%'}; + } + [layout="column"] > [#{$flexName}="#{$i * 5}"] { + max-height: #{$i * 5 + '%'}; + } + } + + [#{$flexName}="33"], [#{$flexName}="34"] { + flex: 0 0 33.33%; + } + [#{$flexName}="66"], [#{$flexName}="67"] { + flex: 0 0 66.66%; + } + + [layout="row"] { + > [#{$flexName}="33"], > [#{$flexName}="34"] { + max-width: 33.33%; + } + > [#{$flexName}="66"], > [#{$flexName}="67"] { + max-width: 66.66%; + } + } + [layout="column"] { + > [#{$flexName}="33"], > [#{$flexName}="34"] { + max-height: 33.33%; + } + > [#{$flexName}="66"], > [#{$flexName}="67"] { + max-height: 66.66%; + } + } +} + +// Alignment attributes for layout containers' children +// Arrange on the Main Axis +// center, start, end, space-between, space-around +// flex-start is the default for justify-content +// ------------------------------ + +@mixin layout-align-for-name($suffix: null) { + $name: 'layout-align'; + @if $suffix != null { + $name: 'layout-align-#{$suffix}'; + } + + // Main Axis Center + [#{$name}="center"], //stretch + [#{$name}="center center"], + [#{$name}="center start"], + [#{$name}="center end"] { + justify-content: center; + } + + // Main Axis End + [#{$name}="end"], //stretch + [#{$name}="end center"], + [#{$name}="end start"], + [#{$name}="end end"] { + justify-content: flex-end; + } + + // Main Axis Space Around + [#{$name}="space-around"], //stretch + [#{$name}="space-around center"], + [#{$name}="space-around start"], + [#{$name}="space-around end"] { + justify-content: space-around; + } + + // Main Axis Space Between + [#{$name}="space-between"], //stretch + [#{$name}="space-between center"], + [#{$name}="space-between start"], + [#{$name}="space-between end"] { + justify-content: space-between; + } + + + // Arrange on the Cross Axis + // center, start, end + // stretch is the default for align-items + // ------------------------------ + + // Cross Axis Center + [#{$name}="center center"], + [#{$name}="start center"], + [#{$name}="end center"], + [#{$name}="space-between center"], + [#{$name}="space-around center"] { + align-items: center; + } + + // Cross Axis Start + [#{$name}="center start"], + [#{$name}="start start"], + [#{$name}="end start"], + [#{$name}="space-between start"], + [#{$name}="space-around start"] { + align-items: flex-start; + } + + // Cross Axis End + [#{$name}="center end"], + [#{$name}="start end"], + [#{$name}="end end"], + [#{$name}="space-between end"], + [#{$name}="space-around end"] { + align-items: flex-end; + } + +} + +// Flex attributes for layout children +// ------------------------------ + +@include flex-properties-for-name(); +@include layout-align-for-name(); +@include flex-order-for-name(); +@include offset-for-name(); + +/** + * `hide-gt-sm show-gt-lg` should hide from 600px to 1200px + * `show-md hide-gt-sm` should show from 0px to 960px and hide at >960px + * `hide-gt-md show-gt-sm` should show everywhere (show overrides hide)` + */ + +// SMALL SCREEN +@media (max-width: $layout-breakpoint-sm - 1) { + [hide-sm], [hide] { + &:not([show-sm]):not([show]) { + display: none; + } + } + + @include flex-order-for-name(sm); + @include layout-align-for-name(sm); + @include layout-for-name(sm); + @include offset-for-name(sm); + @include flex-properties-for-name(sm); +} + +// BIGGER THAN SMALL SCREEN +@media (min-width: $layout-breakpoint-sm) { + @include flex-order-for-name(gt-sm); + @include layout-align-for-name(gt-sm); + @include layout-for-name(gt-sm); + @include offset-for-name(gt-sm); + @include flex-properties-for-name(gt-sm); +} + +// MEDIUM SCREEN +@media (min-width: $layout-breakpoint-sm) and (max-width: $layout-breakpoint-md - 1) { + [hide], [hide-gt-sm] { + &:not([show-gt-sm]):not([show-md]):not([show]) { + display: none; + } + } + [hide-md]:not([show-md]):not([show]) { + display: none; + } + + @include flex-order-for-name(md); + @include layout-align-for-name(md); + @include layout-for-name(md); + @include offset-for-name(md); + @include flex-properties-for-name(md); +} + +// BIGGER THAN MEDIUM SCREEN +@media (min-width: $layout-breakpoint-md) { + @include flex-order-for-name(gt-md); + @include layout-align-for-name(gt-md); + @include layout-for-name(gt-md); + @include offset-for-name(gt-md); + @include flex-properties-for-name(gt-md); +} + +// LARGE SCREEN +@media (min-width: $layout-breakpoint-md) and (max-width: $layout-breakpoint-lg - 1) { + [hide], [hide-gt-sm], [hide-gt-md] { + &:not([show-gt-sm]):not([show-gt-md]):not([show-lg]):not([show]) { + display: none; + } + } + [hide-lg]:not([show-lg]):not([show]) { + display: none; + } + + @include flex-order-for-name(lg); + @include layout-align-for-name(lg); + @include layout-for-name(lg); + @include offset-for-name(lg); + @include flex-properties-for-name(lg); +} + +// BIGGER THAN LARGE SCREEN +@media (min-width: $layout-breakpoint-lg) { + [hide-gt-sm], [hide-gt-md], [hide-gt-lg], [hide] { + &:not([show-gt-sm]):not([show-gt-md]):not([show-gt-lg]):not([show]) { + display: none; + } + } + + @include flex-order-for-name(gt-lg); + @include layout-align-for-name(gt-lg); + @include layout-for-name(gt-lg); + @include offset-for-name(gt-lg); + @include flex-properties-for-name(gt-lg); +} diff --git a/ng2-material/core/style/mixins.scss b/ng2-material/core/style/mixins.scss new file mode 100644 index 00000000..c9f2e5b9 --- /dev/null +++ b/ng2-material/core/style/mixins.scss @@ -0,0 +1,56 @@ +@mixin margin-selectors($before:1em, $after:1em, $start:0px, $end:0px) { + -webkit-margin-before: $before; + -webkit-margin-after: $after; + -webkit-margin-start: $start; + -webkit-margin-end: $end; +} + +@mixin not-selectable($value:none) { + -webkit-touch-callout: $value; + -webkit-user-select: $value; + -khtml-user-select: $value; + -moz-user-select: $value; + -ms-user-select: $value; + user-select: $value; +} + +@mixin input-placeholder-color($color) { + &::-webkit-input-placeholder, + &::-moz-placeholder, /* Firefox 19+ */ + &:-moz-placeholder, /* Firefox 18- */ + &:-ms-input-placeholder { + color: $color; + } +} + +@mixin pie-clearfix { + &:after { + content: ''; + display: table; + clear: both; + } +} + +@function map-to-string($map) { + $map-str: '{'; + $keys: map-keys($map); + $len: length($keys); + @for $i from 1 through $len { + $key: nth($keys, $i); + $value: map-get($map, $key); + $map-str: $map-str + '_' + $key + '_: _' + map-get($map, $key) + '_'; + @if $i != $len { + $map-str: $map-str + ','; + } + } + @return $map-str + '}'; +} + + +/* mixin definition ; sets LTR and RTL within the same style call */ +@mixin rtl($prop, $value, $rtl-value) { + #{$prop}: $value; + html[dir=rtl] & { + #{$prop}: $rtl-value; + } +} diff --git a/ng2-material/core/style/palette.scss b/ng2-material/core/style/palette.scss new file mode 100644 index 00000000..2f5bf3e9 --- /dev/null +++ b/ng2-material/core/style/palette.scss @@ -0,0 +1,666 @@ +// Core color palettes. +$md-red: ( + 50: #ffebee, + 100: #ffcdd2, + 200: #ef9a9a, + 300: #e57373, + 400: #ef5350, + 500: #f44336, + 600: #e53935, + 700: #d32f2f, + 800: #c62828, + 900: #b71c1c, + A100: #ff8a80, + A200: #ff5252, + A400: #ff1744, + A700: #d50000, +); + +$md-pink: ( + 50: #fce4ec, + 100: #f8bbd0, + 200: #f48fb1, + 300: #f06292, + 400: #ec407a, + 500: #e91e63, + 600: #d81b60, + 700: #c2185b, + 800: #ad1457, + 900: #880e4f, + A100: #ff80ab, + A200: #ff4081, + A400: #f50057, + A700: #c51162, +); + +$md-purple: ( + 50: #f3e5f5, + 100: #e1bee7, + 200: #ce93d8, + 300: #ba68c8, + 400: #ab47bc, + 500: #9c27b0, + 600: #8e24aa, + 700: #7b1fa2, + 800: #6a1b9a, + 900: #4a148c, + A100: #ea80fc, + A200: #e040fb, + A400: #d500f9, + A700: #aa00ff, +); + +$md-deep-purple: ( + 50: #ede7f6, + 100: #d1c4e9, + 200: #b39ddb, + 300: #9575cd, + 400: #7e57c2, + 500: #673ab7, + 600: #5e35b1, + 700: #512da8, + 800: #4527a0, + 900: #311b92, + A100: #b388ff, + A200: #7c4dff, + A400: #651fff, + A700: #6200ea, +); + +$md-indigo: ( + 50: #e8eaf6, + 100: #c5cae9, + 200: #9fa8da, + 300: #7986cb, + 400: #5c6bc0, + 500: #3f51b5, + 600: #3949ab, + 700: #303f9f, + 800: #283593, + 900: #1a237e, + A100: #8c9eff, + A200: #536dfe, + A400: #3d5afe, + A700: #304ffe, +); + +$md-blue: ( + 50: #e3f2fd, + 100: #bbdefb, + 200: #90caf9, + 300: #64b5f6, + 400: #42a5f5, + 500: #2196f3, + 600: #1e88e5, + 700: #1976d2, + 800: #1565c0, + 900: #0d47a1, + A100: #82b1ff, + A200: #448aff, + A400: #2979ff, + A700: #2962ff, +); + +$md-light-blue: ( + 50: #e1f5fe, + 100: #b3e5fc, + 200: #81d4fa, + 300: #4fc3f7, + 400: #29b6f6, + 500: #03a9f4, + 600: #039be5, + 700: #0288d1, + 800: #0277bd, + 900: #01579b, + A100: #80d8ff, + A200: #40c4ff, + A400: #00b0ff, + A700: #0091ea, +); + +$md-cyan: ( + 50: #e0f7fa, + 100: #b2ebf2, + 200: #80deea, + 300: #4dd0e1, + 400: #26c6da, + 500: #00bcd4, + 600: #00acc1, + 700: #0097a7, + 800: #00838f, + 900: #006064, + A100: #84ffff, + A200: #18ffff, + A400: #00e5ff, + A700: #00b8d4, +); + +$md-teal: ( + 50: #e0f2f1, + 100: #b2dfdb, + 200: #80cbc4, + 300: #4db6ac, + 400: #26a69a, + 500: #009688, + 600: #00897b, + 700: #00796b, + 800: #00695c, + 900: #004d40, + A100: #a7ffeb, + A200: #64ffda, + A400: #1de9b6, + A700: #00bfa5, +); + +$md-green: ( + 50: #e8f5e9, + 100: #c8e6c9, + 200: #a5d6a7, + 300: #81c784, + 400: #66bb6a, + 500: #4caf50, + 600: #43a047, + 700: #388e3c, + 800: #2e7d32, + 900: #1b5e20, + A100: #b9f6ca, + A200: #69f0ae, + A400: #00e676, + A700: #00c853, +); + +$md-light-green: ( + 50: #f1f8e9, + 100: #dcedc8, + 200: #c5e1a5, + 300: #aed581, + 400: #9ccc65, + 500: #8bc34a, + 600: #7cb342, + 700: #689f38, + 800: #558b2f, + 900: #33691e, + A100: #ccff90, + A200: #b2ff59, + A400: #76ff03, + A700: #64dd17, +); + +$md-lime: ( + 50: #f9fbe7, + 100: #f0f4c3, + 200: #e6ee9c, + 300: #dce775, + 400: #d4e157, + 500: #cddc39, + 600: #c0ca33, + 700: #afb42b, + 800: #9e9d24, + 900: #827717, + A100: #f4ff81, + A200: #eeff41, + A400: #c6ff00, + A700: #aeea00, +); + +$md-yellow: ( + 50: #fffde7, + 100: #fff9c4, + 200: #fff59d, + 300: #fff176, + 400: #ffee58, + 500: #ffeb3b, + 600: #fdd835, + 700: #fbc02d, + 800: #f9a825, + 900: #f57f17, + A100: #ffff8d, + A200: #ffff00, + A400: #ffea00, + A700: #ffd600, +); + +$md-amber: ( + 50: #fff8e1, + 100: #ffecb3, + 200: #ffe082, + 300: #ffd54f, + 400: #ffca28, + 500: #ffc107, + 600: #ffb300, + 700: #ffa000, + 800: #ff8f00, + 900: #ff6f00, + A100: #ffe57f, + A200: #ffd740, + A400: #ffc400, + A700: #ffab00, +); + +$md-orange: ( + 50: #fff3e0, + 100: #ffe0b2, + 200: #ffcc80, + 300: #ffb74d, + 400: #ffa726, + 500: #ff9800, + 600: #fb8c00, + 700: #f57c00, + 800: #ef6c00, + 900: #e65100, + A100: #ffd180, + A200: #ffab40, + A400: #ff9100, + A700: #ff6d00, +); + +$md-deep-orange: ( + 50: #fbe9e7, + 100: #ffccbc, + 200: #ffab91, + 300: #ff8a65, + 400: #ff7043, + 500: #ff5722, + 600: #f4511e, + 700: #e64a19, + 800: #d84315, + 900: #bf360c, + A100: #ff9e80, + A200: #ff6e40, + A400: #ff3d00, + A700: #dd2c00, +); + +$md-brown: ( + 50: #efebe9, + 100: #d7ccc8, + 200: #bcaaa4, + 300: #a1887f, + 400: #8d6e63, + 500: #795548, + 600: #6d4c41, + 700: #5d4037, + 800: #4e342e, + 900: #3e2723, + A100: #d7ccc8, + A200: #bcaaa4, + A400: #8d6e63, + A700: #5d4037, +); + +$md-grey: ( + 0: #ffffff, + 50: #fafafa, + 100: #f5f5f5, + 200: #eeeeee, + 300: #e0e0e0, + 400: #bdbdbd, + 500: #9e9e9e, + 600: #757575, + 700: #616161, + 800: #424242, + 900: #212121, + 1000: #000000, + A100: #ffffff, + A200: #eeeeee, + A400: #bdbdbd, + A700: #616161, +); + +$md-blue-grey: ( + 50: #eceff1, + 100: #cfd8dc, + 200: #b0bec5, + 300: #90a4ae, + 400: #78909c, + 500: #607d8b, + 600: #546e7a, + 700: #455a64, + 800: #37474f, + 900: #263238, + A100: #cfd8dc, + A200: #b0bec5, + A400: #78909c, + A700: #455a64, +); + + +$md-light-theme-foreground: ( + divider: rgba(black, 0.12), + dividers: rgba(black, 0.12), + disabled: rgba(black, 0.26), + disabled-text: rgba(black, 0.26), + hint-text: rgba(black, 0.26), + secondary-text: rgba(black, 0.54), + icon: rgba(black, 0.54), + icons: rgba(black, 0.54), + text: rgba(black, 0.87) +); + +$md-dark-theme-foreground: ( + divider: rgba(white, 0.12), + dividers: rgba(white, 0.12), + disabled: rgba(white, 0.30), + disabled-text: rgba(white, 0.30), + hint-text: rgba(white, 0.30), + secondary-text: rgba(white, 0.70), + icon: white, + icons: white, + text: white +); + + +// Contrast colors. These are hard-coded because it is too difficult to calculate them. +// These contrast colors are pulled from the public Material Design spec swatches. While the +// contrast colors in the spec are not perscriptive, we will use them for convenience. +$black-87-opacity: rgba(black, 0.870588); +$white-87-opacity: rgba(white, 0.870588); + +$md-contrast-palettes: ( + $md-red: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-pink: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-purple: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-deep-purple: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: $white-87-opacity, + A700: $white-87-opacity, + ), + $md-indigo: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: $white-87-opacity, + ), + $md-blue: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-light-blue: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: white, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: white, + ), + $md-cyan: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: white, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-teal: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-green: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-light-green: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: white, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-lime: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: $black-87-opacity, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-yellow: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: $black-87-opacity, + 900: $black-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-amber: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: $black-87-opacity, + 900: $black-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-orange: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: white, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: black, + ), + $md-deep-orange: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: white, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: white, + A700: white, + ), + $md-brown: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: white, + A700: $white-87-opacity, + ), + $md-grey: ( + 0: $black-87-opacity, + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + 1000: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $white-87-opacity, + ), + $md-blue-grey: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: white, + 500: white, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: white, + A700: $white-87-opacity, + ), +); diff --git a/ng2-material/core/style/shadows.scss b/ng2-material/core/style/shadows.scss new file mode 100644 index 00000000..24267e02 --- /dev/null +++ b/ng2-material/core/style/shadows.scss @@ -0,0 +1,15 @@ +// Elements can have an "elevation" from 1 to 5, signified by shadows. +// See http://google.com/design/spec/what-is-material/objects-in-3d-space.html + +$md-shadow-bottom-z-1: 0 2px 5px 0 rgba(0, 0, 0, 0.26); +$md-shadow-bottom-z-2: 0 4px 8px 0 rgba(0, 0, 0, 0.4); + + +.md-whiteframe-z1 { + box-shadow: $whiteframe-shadow-z1; + z-index: $whiteframe-zindex-z1; +} +.md-whiteframe-z2 { + box-shadow: $whiteframe-shadow-z2; + z-index: $whiteframe-zindex-z2; +} diff --git a/ng2-material/core/style/structure.scss b/ng2-material/core/style/structure.scss new file mode 100644 index 00000000..f3aa5975 --- /dev/null +++ b/ng2-material/core/style/structure.scss @@ -0,0 +1,180 @@ +html, body { + height: 100%; + color: rgba(0,0,0,0.87); + background: white; + position: relative; +} + +body { + margin: 0; + padding: 0; +} + +[tabindex='-1']:focus { + outline: none; +} +.inset { + padding: 10px; +} + +button.md-no-style { + font-weight: normal; + background-color: inherit; + text-align: left; + border: none; + padding: 0; + margin: 0; +} + +select, +button, +textarea, +input { + vertical-align: baseline; +} + +// Fix Android 4.0 button bugs +input[type="reset"], +input[type="submit"], +html input[type="button"], +button { + cursor: pointer; + -webkit-appearance: button; + + &[disabled] { + cursor: default; + } +} + +textarea { + vertical-align: top; + overflow: auto; +} + +input { + &[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box; + -webkit-box-sizing: content-box; + + &::-webkit-search-decoration, + &::-webkit-search-cancel-button { + -webkit-appearance: none; + } + } +} + +.md-visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + text-transform: none; + width: 1px; +} + +.md-shadow { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: inherit; + pointer-events: none; +} + +.md-shadow-bottom-z-1 { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); +} +.md-shadow-bottom-z-2 { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); +} + +.md-shadow-animated.md-shadow { + transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* + * A container inside of a rippling element (eg a button), + * which contains all of the individual ripples + */ +.md-ripple-container { + pointer-events: none; + position: absolute; + overflow: hidden; + left: 0; + top: 0; + width: 100%; + height: 100%; + transition: all 0.55s $swift-ease-out-timing-function; +} + +.md-ripple { + position: absolute; + transform: scale(0); + transform-origin: 50% 50%; + opacity: 0; + border-radius: 50%; + &.md-ripple-placed { + $positionDuration: 0.9s * 2; + $sizeDuration: 0.65s * 2; + transition: left $positionDuration $swift-ease-out-timing-function, + top $positionDuration $swift-ease-out-timing-function, + margin $sizeDuration $swift-ease-out-timing-function, + border $sizeDuration $swift-ease-out-timing-function, + width $sizeDuration $swift-ease-out-timing-function, + height $sizeDuration $swift-ease-out-timing-function, + opacity $sizeDuration $swift-ease-out-timing-function, + transform $sizeDuration $swift-ease-out-timing-function; + } + &.md-ripple-scaled { + transform: scale(1); + } + &.md-ripple-active, &.md-ripple-full, &.md-ripple-visible { + opacity: 0.20; + } +} + +.md-padding { + padding: 8px; +} + +.md-margin { + margin: 8px; +} + +.md-scroll-mask { + position: absolute; + background-color: transparent; + top: 0; + right: 0; + bottom: 0; + left: 0; + > .md-scroll-mask-bar { + display: block; + position: absolute; + background-color: #fafafa; + right: 0; + top: 0; + bottom: 0; + z-index: $z-index-scroll-mask; + box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.3) + } +} + +.md-no-select { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +@media (min-width: $layout-breakpoint-sm) { + .md-padding { + padding: 16px; + } +} diff --git a/ng2-material/core/style/theme-functions.scss b/ng2-material/core/style/theme-functions.scss new file mode 100644 index 00000000..4a3537ee --- /dev/null +++ b/ng2-material/core/style/theme-functions.scss @@ -0,0 +1,62 @@ +// ** Two main functions for users ** +// md-palette: used for defining your theme in terms of Material hues. +// md-color: apply colors to components from the palette. Consumes the output of md-palette + +// Implement the rem unit with SCSS so we don't have to actually set a font-size on +// the user's body element. +@function rem($multiplier) { + $font-size: 10px; + @return $multiplier * $font-size; +} + +// For a given hue in a palette, return the contrast color from the map of contrast palettes. +@function md-contrast($color-map, $hue, $contrast-color-map) { + @return map-get(map-get($contrast-color-map, $color-map), $hue); +} + + +// Creates a map of hues to colors for a theme. +// $color-map +// $primary +// $lighter +// $darker +@function md-palette($color-map, $primary, $lighter, $darker, $contrast-color-map) { + $result: map_merge($color-map, ( + default: map-get($color-map, $primary), + lighter: map-get($color-map, $lighter), + darker: map-get($color-map, $darker), + + default-contrast: md-contrast($color-map, $primary, $contrast-color-map), + lighter-contrast: md-contrast($color-map, $lighter, $contrast-color-map), + darker-contrast: md-contrast($color-map, $darker, $contrast-color-map) + )); + + // For each hue in the palette, add a "-contrast" color to the map. + @each $hue, $color in $color-map { + $result: map_merge($result, ( + "#{$hue}-contrast": md-contrast($color-map, $hue, $contrast-color-map) + )) + } + + @return $result; +} + +// Gets a color for a material design component. +// $color-map: a map of {key: color}. +// $hue-key: key used to lookup the color in $colorMap. Defaults to 'default' +// If $hue-key is a number between 0 and 1, it will be treated as $opacity. +// $opacity: the opacity to apply to the color. +@function md-color($color-map, $hue-key: default, $opacity: 1) { + // If hueKey is a number between zero and one, then it actually contains an + // opacity value, so recall this function with the default hue and that given opacity. + @if type-of($hue-key) == number and $hue-key >= 0 and $hue-key <= 1 { + @return md-color($color-map, default, $hue-key) + } + + $color: map-get($color-map, $hue-key); + $opacity: if(opacity($color) < 1, opacity($color), $opacity); + + @return rgba($color, $opacity); +} + + diff --git a/ng2-material/core/style/typography.scss b/ng2-material/core/style/typography.scss new file mode 100644 index 00000000..43c42d2b --- /dev/null +++ b/ng2-material/core/style/typography.scss @@ -0,0 +1,94 @@ +html, body { + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-touch-callout: none; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; +} + +md-select, md-card, md-list, md-toolbar, +ul, ol, p, h1, h2, h3, h4, h5, h6 { + text-rendering: optimizeLegibility; +} + +/************ + * Headings + ************/ +.md-display-4 { + font-size: $display-4-font-size-base; + font-weight: 300; + letter-spacing: -0.010em; + line-height: $display-4-font-size-base; +} +.md-display-3 { + font-size: $display-3-font-size-base; + font-weight: 400; + letter-spacing: -0.005em; + line-height: $display-3-font-size-base; +} +.md-display-2 { + font-size: $display-2-font-size-base; + font-weight: 400; + line-height: rem(6.4); +} +.md-display-1 { + font-size: $display-1-font-size-base; + font-weight: 400; + line-height: rem(4); +} +.md-headline { + font-size: $headline-font-size-base; + font-weight: 400; + line-height: rem(3.2); +} +.md-title { + font-size: $title-font-size-base; + font-weight: 500; + letter-spacing: 0.005em; +} +.md-subhead { + font-size: $subhead-font-size-base; + font-weight: 400; + letter-spacing: 0.010em; + line-height: rem(2.4); +} +/************ + * Body Copy + ************/ +.md-body-1 { + font-size: $body-font-size-base; + font-weight: 400; + letter-spacing: 0.010em; + line-height: rem(2); +} +.md-body-2 { + font-size: $body-font-size-base; + font-weight: 500; + letter-spacing: 0.010em; + line-height: rem(2.4); +} +.md-caption { + font-size: $caption-font-size-base; + letter-spacing: 0.020em; +} +.md-button { + letter-spacing: 0.010em; +} + +/************ + * Defaults + ************/ + +button, +select, +html, +textarea, +input { + font-family: $font-family; +} + +select, +button, +textarea, +input { + font-size: 100%; +} diff --git a/ng2-material/core/style/variables.scss b/ng2-material/core/style/variables.scss new file mode 100644 index 00000000..72df551e --- /dev/null +++ b/ng2-material/core/style/variables.scss @@ -0,0 +1,78 @@ +@import 'theme-functions'; + +// Typography +// ------------------------------ +$font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif !default; +$font-size: 10px; + +$display-4-font-size-base: rem(11.20) !default; +$display-3-font-size-base: rem(5.600) !default; +$display-2-font-size-base: rem(4.500) !default; +$display-1-font-size-base: rem(3.400) !default; +$headline-font-size-base: rem(2.400) !default; +$title-font-size-base: rem(2.000) !default; +$subhead-font-size-base: rem(1.600) !default; + +$body-font-size-base: rem(1.400) !default; +$caption-font-size-base: rem(1.200) !default; + +// Layout +$baseline-grid: 8px !default; +$layout-breakpoint-sm: 600px !default; +$layout-breakpoint-md: 960px !default; +$layout-breakpoint-lg: 1200px !default; +$layout-gutter-width: ($baseline-grid * 2) !default; + +// Typography +$md-body-font-size-base: rem(1.400) !default; + + +// App bar variables +$app-bar-height: 64px; + +// Icons +$icon-size: rem(2.4); + + + +// Toast +$toast-height: $baseline-grid * 3 !default; +$toast-margin: $baseline-grid * 1 !default; + + +// Whiteframes +$whiteframe-shadow-z1: 0px 2px 5px 0 rgba(0,0,0,0.26) !default; +$whiteframe-zindex-z1: 1 !default; +$whiteframe-shadow-z2: 0px 8px 17px rgba(0,0,0,0.2) !default; +$whiteframe-zindex-z2: 2 !default; +$whiteframe-shadow-z3: 0px 17px 50px rgba(0,0,0,0.19) !default; +$whiteframe-zindex-z3: 3 !default; +$whiteframe-shadow-z4: 0px 16px 28px 0 rgba(0,0,0,0.22) !default; +$whiteframe-zindex-z4: 4 !default; +$whiteframe-shadow-z5: 0px 27px 24px 0 rgba(0,0,0,0.2) !default; +$whiteframe-zindex-z5: 5 !default; + + +// Z-indexes +$z-index-tooltip: 100 !default; +$z-index-dialog: 80 !default; +$z-index-toast: 90 !default; +$z-index-bottom-sheet: 70 !default; +$z-index-scroll-mask: 65 !default; +$z-index-sidenav: 60 !default; +$z-index-backdrop: 50 !default; +$z-index-fab: 20 !default; + + +// Easing Curves +$swift-ease-out-duration: 0.4s !default; +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +$swift-ease-in-duration: 0.3s !default; +$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; + +$swift-ease-in-out-duration: 0.5s !default; +$swift-ease-in-out-timing-function: cubic-bezier(0.35, 0, 0.25, 1) !default; +$swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default; diff --git a/package.json b/package.json new file mode 100644 index 00000000..f67e1c0a --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "pow-node", + "version": "0.0.0", + "description": "Server node for pow-edit based project. Perform user auth and storage of game data.", + "main": "source/server.js", + "repository": { + "type": "git", + "url": "git@github.com:justindujardin/pow-node.git" + }, + "author": "Justin DuJardin ", + "license": "MIT+BSD Dual License", + "readmeFilename": "README.md", + "scripts": { + "test": "npm install && tslint source/**/*.ts" + }, + "dependencies": { + "angular2": "2.0.0-alpha.48", + "systemjs": "0.19.2" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-cli": "^0.1.13", + "grunt-contrib-clean": "^0.7.0", + "grunt-contrib-sass": "^0.9.2", + "grunt-contrib-watch": "^0.6.1", + "grunt-http-server": "^1.13.0", + "grunt-notify": "^0.4.3", + "grunt-ts": "^5.2.0", + "systemjs-builder": "justindujardin/builder.git#typescript-source-maps", + "tslint": "^3.1.1", + "typescript": "^1.7.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..797ce31c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "version": "1.6.0", + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true + }, + "exclude": [ + "node_modules" + ], + "compileOnSave": true +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..84ad6a4b --- /dev/null +++ b/tslint.json @@ -0,0 +1,62 @@ +{ + "rules": { + "class-name": true, + "comment-format": [true, "check-space"], + "curly": true, + "eofline": true, + "forin": true, + "indent": [true, "spaces"], + "label-position": true, + "label-undefined": true, + "max-line-length": [true, 120], + "member-access": true, + "member-ordering": [true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-inferrable-types": false, + "no-shadowed-variable": true, + "no-string-literal": true, + "no-switch-case-fall-through": true, + "no-trailing-comma": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-unreachable": true, + "no-use-before-declare": true, + "no-var-keyword": false, + "object-literal-sort-keys": false, + "one-line": [true, + "check-open-brace", + "check-whitespace" + ], + "quotemark": [true, "single"], + "radix": true, + "semicolon": true, + "triple-equals": [true, "allow-null-check"], + "variable-name": false, + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +}
Card content
+ * Card content + *
{{item.description}}