);
}
================================================
FILE: docs/src/js/pages/CardPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Card from "@react-mdc/card";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
import * as Image1x1 from "app/images/1-1.jpg";
import * as Image16x9 from "app/images/16-9.jpg";
export default function CardPage() {
return (
Card
Title goes here
Subtitle here
ACTION 1
ACTION 2
Button component is a React wrapper of mdc-card component.
Installation
Usage
Simple Card
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor.
`} />
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor.
Media Card
Title goes here
Subtitle here
ACTION 1
ACTION 2
`} />
Title goes here
Subtitle here
ACTION 1
ACTION 2
ComponentsCardNameCardDescription
Top-level container of card components.
Properties
Property
Type
Required
Description
dark
boolean
Render dark card.
ActionsNameCard.ActionsDescription
A container of action components.
Properties
Property
Type
Required
Description
vertical
boolean
Render vertical actions section.
ActionNameCard.ActionDescription
Card action button
Horizontal BlockNameCard.HorizontalBlockDescription
Horizontal block component.
MediaNameCard.MediaDescription
Media section component.
Media ItemNameCard.MediaItemDescription
Media item component.
Property
Type
Required
Description
size
1.5 | 2 | 3
Size of media item.
PrimaryNameCard.PrimaryDescription
Primary section component.
SubtitleNameCard.SubtitleDescription
Subtitle of card.
Supporting TextNameCard.SupportingTextDescription
Supporting text section.
TitleNameCard.TitleDescription
Title of card.
);
}
================================================
FILE: docs/src/js/pages/CheckboxPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Checkbox from "@react-mdc/checkbox";
import FormField from "@react-mdc/form-field";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function CheckboxPage() {
return (
Checkbox
Checkbox component is a React wrapper of mdc-checkbox component.
Installation
Usage
Simple Checkbox
`} />
Cutomizable Checkbox
alert("Changed: " + x) } />
`} />
{/* tslint:disable:jsx-no-lambda */}
alert("Changed: " + x.target.value)} />
{/* tslint:enable:jsx-no-lambda */}
ComponentsCheckboxNameCheckboxDescription
Top-level container of checkbox components.
Properties
Property
Type
Required
Description
checked
boolean
Render checked checkbox.
disabled
boolean
Render disabled checkbox.
indeterminate
boolean
Render indeterminate state checkbox.
BackgroundNameCheckbox.BackgroundDescription
Background component of checkbox.
CheckmarkNameCheckbox.CheckmarkDescription
Checkmark component of checkbox. It's a just SVG component.
So it doesn't have a meta component.
MixedmarkNameCheckbox.MixedmarkDescription
Mixedmark component of checkbox.
NativeControlNameCheckbox.NativeControlDescription
Native control (actual input component) component of checkbox.
Properties
Property
Type
Required
Description
onChange
(event) => void
Handler for change event.
DefaultNameCheckbox.DefaultDescription
Default composition of checkbox component
Properties
Property
Type
Required
Description
inputId
string
ID property for native control
name
string
Name property for native control.
value
any
Value property for native control.
onChange
(event) => void
Handler for change event.
checked
boolean
Render checked checkbox.
disabled
boolean
Render disabled checkbox.
indeterminate
boolean
Render indeterminate state checkbox.
defaultChecked
boolean
Default checked state of checkbox.
);
}
================================================
FILE: docs/src/js/pages/DialogPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Button from "@react-mdc/button";
import Dialog from "@react-mdc/dialog";
import FormField from "@react-mdc/form-field";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
class SimpleDialog extends React.Component<{}, {}> {
public state = {
open: false,
};
public render() {
return (
);
}
private handleOpen = () => this.setState({ open: true });
private handleClose = () => this.setState({ open: false });
}
export default function DialogPage() {
return (
Dialog
Dialog component is a React wrapper of mdc-dialog component.
Installation
Usage
Simple Dialog
{
public state = {
open: false,
};
public render() {
return (
HeaderNameDialog.HeaderDescription
Dialog header section.
Header TitleNameDialog.Header.TitleDescription
Dialog header title component.
);
}
================================================
FILE: docs/src/js/pages/ElevationPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Elevation from "@react-mdc/elevation";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function ElevationPage() {
return (
Elevation
Elevation
Elevation component is a React wrapper of mdc-elevation component.
Installation
Usage
Elevation
Z-Space: 10`} />
Z-Space: 10ElevationNameElevationProperties
Property
Type
Required
Description
zSpace
number (0 ~ 24)
✔
z-space of elevation component
transition
boolean
Render elevation with transition animation.
);
}
================================================
FILE: docs/src/js/pages/FABPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import FAB from "@react-mdc/fab";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import { MaterialIcon } from "app/js/components/Icon";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function CheckboxPage() {
return (
FAB
edit
FAB component is a React wrapper of mdc-fab (Float Action Button) component.
Installation
Usage
Simple FAB
add
`} />
addComponentsFABNameFABDescription
Top-level container of FAB components.
Properties
Property
Type
Required
Description
mini
boolean
Render mini FAB.
plain
boolean
Render plain FAB.
IconNameFAB.IconDescription
FAB icon component.
);
}
================================================
FILE: docs/src/js/pages/FormFieldPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Checkbox from "@react-mdc/checkbox";
import FormField from "@react-mdc/form-field";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function FormFieldPage() {
return (
Form Field
FormField component is a React wrapper of mdc-form-field component.
Installation
Usage
FormField
`} />
FormFieldNameFormFieldProperties
Property
Type
Required
Description
alignEnd
boolean
Align contents to end.
);
}
================================================
FILE: docs/src/js/pages/LayoutGridPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import LayoutGrid from "@react-mdc/layout-grid";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import { MaterialIcon } from "app/js/components/Icon";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function LayoutGridPage() {
return (
Layout Grid
LayoutGrid component is a React wrapper of mdc-layout-grid component.
Installation
Usage
Simple Grid
`} />
ComponentsLayout GridNameLayoutGridDescription
Top-level container of layout grid components.
Properties
);
}
================================================
FILE: docs/src/js/pages/NotFoundPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import Button from "@react-mdc/button";
import Card from "@react-mdc/card";
import FullSize from "app/js/components/FullSize";
export default class NotFound extends React.Component<{}, {}> {
public render() {
return (
Sorry, but nothing in here
Please check your URL and try again.
Go to main
);
}
}
================================================
FILE: docs/src/js/pages/RadioPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import FormField from "@react-mdc/form-field";
import Radio from "@react-mdc/radio";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function RadioPage() {
return (
Radio
Radio component is a React wrapper of mdc-radio component.
Installation
Usage
Simple Radio
`} />
Cutomizable Radio
alert(e.target.value)}
defaultChecked
value="left" />
`} />
{/* tslint:disable:jsx-no-lambda */}
alert(e.target.value)}
defaultChecked
value="left" />
{/* tslint:enable:jsx-no-lambda */}
ComponentsRadioNameRadioDescription
Top-level container of radio components.
Properties
Property
Type
Required
Description
checked
boolean
Render checked radio.
disabled
boolean
Render disabled radio.
BackgroundNameRadio.BackgroundDescription
Background component of radio.
InnerCircleNameRadio.InnerCircleDescription
InnerCircle component of radio.
NativeControlNameRadio.NativeControlDescription
Native control (actual input component) component of radio.
Properties
Property
Type
Required
Description
onChange
(event) => void
Handler for change event.
OuterCircleNameRadio.OuterCircleDescription
OuterCircle component of radio.
DefaultNameRadio.DefaultDescription
Simpe, common composition of radio component
Properties
Property
Type
Required
Description
inputId
string
ID property for native control
name
string
Name property for native control.
value
any
Value property for native control.
onChange
(event) => void
Handler for change event.
checked
boolean
Render checked radio.
disabled
boolean
Render disabled radio.
defaultChecked
boolean
Default checked state of radio.
);
}
================================================
FILE: docs/src/js/pages/RipplePage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Button from "@react-mdc/button";
import Elevation from "@react-mdc/elevation";
import Ripple from "@react-mdc/ripple";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function RipplePage() {
return (
Ripple
RIPPLE
Ripple component is a React wrapper of mdc-ripple component.
Installation
Usage
Simple Ripple
Click Me!
`} />
Click Me!
Ripple Button
`} />
RippleNameRippleProperties
Property
Type
Required
Description
unbounded
boolean
Unbounded ripple?
color
"primary" | "accent
Color of interaction.
);
}
================================================
FILE: docs/src/js/pages/SwitchPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import FormField from "@react-mdc/form-field";
import Switch from "@react-mdc/switch";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function SwitchPage() {
return (
Switch
Switch
Switch component is a React wrapper of mdc-switch component.
Installation
Usage
Simple Switch
`} />
Cutomizable Switch
alert("On: " + e.target.value)} />
`} />
{/* tslint:disable:jsx-no-lambda */}
alert("On: " + e.currentTarget.value)} />
{/* tslint:enable:jsx-no-lambda */}
ComponentsSwitchNameSwitchDescription
Top-level container of switch components.
Properties
Property
Type
Required
Description
checked
boolean
Render checked switch.
disabled
boolean
Render disabled switch.
BackgroundNameSwitch.BackgroundDescription
Background component of switch.
KnobNameSwitch.KnobDescription
Knob component of switch.
LabelNameSwitch.LabelDescription
Switch label component
NativeControlNameSwitch.NativeControlDescription
Native control (actual input component) component of switch.
Properties
Property
Type
Required
Description
onChange
(event) => void
Handler for change event.
DefaultNameSwitch.DefaultDescription
Simpe, common composition of switch component
Properties
Property
Type
Required
Description
inputId
string
ID property for native control
name
string
Name property for native control.
value
any
Value property for native control.
onChange
(event) => void
Handler for change event.
checked
boolean
Render checked switch.
disabled
boolean
Render disabled switch.
defaultChecked
boolean
Default checked state of switch.
);
}
================================================
FILE: docs/src/js/pages/TextfieldPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Textfield from "@react-mdc/textfield";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function TextfieldPage() {
return (
Textfield
Textfield component is a React wrapper of mdc-textfield component.
Installation
Usage
Simple Textfield
`} />
Labeled
Email Address
`} />
Email Address
Multiline
Comment
`} />
Comment
Full Width
`} />
ComponentsTextfieldNameTextfieldDescription
Top-level container of textfield components.
Properties
Property
Type
Required
Description
disabled
boolean
Disabled state.
multiline
boolean
Enable multiline
fullwidth
boolean
Render full width textfield
InputNameTextfield.InputDescription
Input component of textfield.
LabelNameText.field.LabelDescription
Textfield label
);
}
================================================
FILE: docs/src/js/pages/TypographyPage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import ComponentPage from "app/js/components/ComponentPage";
import { MaterialIcon } from "app/js/components/Icon";
import Name from "app/js/components/Name";
import ShowCase from "app/js/components/ShowCase";
import Table from "app/js/components/Table";
export default function CheckboxPage() {
return (
Typography
Aa 가
Typography component is a React wrapper of mdc-typography component.
Installation
Usage
Title
This is a Title
`} />
This is a Title
Meta
This is a caption paragraph.
`} />
This is a caption paragraph.
ComponentsTypographyNameTypographyDescription
Top-level container of typography components.
Every typography texts should be included in this component.
TextNameTypography.TextDescription
Text component of typography.
Properties
Property
Type
Required
Description
textStyle
String value of list at below
✔
Style of text component.
adjustMargin
boolean
Enable adjustment of component's margin.
Text Styles
display4
display3
display2
display1
headline
title
subheading2
subheading1
body2
body1
caption
ShortcutsNameTypography.Display4Typography.Display3Typography.Display2Typography.Display1Typography.HeadlineTypography.TitleTypography.Subheading2Typography.Subheading1Typography.Body2Typography.Body1Typography.CaptionDescription
There are coresponding shortcut components for each text styles.
Shorcuts don't have meta components. Use Text.Meta
instead if you want a meta component.
Properties
Property
Type
Required
Description
adjustMargin
boolean
Enable adjustment of component's margin.
);
}
================================================
FILE: docs/src/js/pages/WelcomePage/index.tsx
================================================
import * as React from "react";
import { Link } from "react-router";
import * as classNames from "classnames";
import Button from "@react-mdc/button";
import LayoutGrid from "@react-mdc/layout-grid";
import Ripple from "@react-mdc/ripple";
import Theme from "@react-mdc/theme";
import Typography from "@react-mdc/typography";
import Code from "app/js/components/Code";
import DarkTheme from "app/js/components/DarkTheme";
import Page from "app/js/components/Page";
import * as styles from "./styles.scss";
function Welcome() {
return (
React Material Components for the Web
React wrapper of Google's Material Components for the Web
Components
Source Code
{/* Placeholder */}
);
}
function GettingStarted() {
return (
Getting Started
Get up and running with React Material Components web
Installation
You can install the whole react-material-components-web components by following
To install each components individually
Load Stylesheet
Since Material Components for Web provides customization by stylesheet,
You have to load the stylesheet manually.
Using Components
Now you can use components.
Hello
)
}
`} />
);
}
export default function WelcomePage() {
return (
);
}
================================================
FILE: docs/src/js/pages/WelcomePage/styles.scss
================================================
@import "style/vars/theme";
:local {
.flex {
flex: 1;
}
.welcome {
padding-bottom: 60px;
}
.welcome-link {
text-decoration: none;
border-bottom: solid 1px $mdc-theme-primary;
padding: 0.2em;
margin: 0.2em;
}
}
================================================
FILE: docs/src/js/routes.tsx
================================================
import * as React from "react";
import { hashHistory, Route, Router } from "react-router";
import ComponentPage from "app/js/components/ComponentPage";
import Container from "./Container";
import ButtonPage from "./pages/ButtonPage";
import CardPage from "./pages/CardPage";
import CheckboxPage from "./pages/CheckboxPage";
import DialogPage from "./pages/DialogPage";
import ElevationPage from "./pages/ElevationPage";
import FABPage from "./pages/FABPage";
import FormFieldPage from "./pages/FormFieldPage";
import LayoutGridPage from "./pages/LayoutGridPage";
import RadioPage from "./pages/RadioPage";
import RipplePage from "./pages/RipplePage";
import SwitchPage from "./pages/SwitchPage";
import TextfieldPage from "./pages/TextfieldPage";
import TypographyPage from "./pages/TypographyPage";
import NotFoundPage from "./pages/NotFoundPage";
import WelcomePage from "./pages/WelcomePage";
function MainContainer(props) {
let {
children,
...p,
} = props;
children = children || ;
return ;
}
function scrollToTop() {
window.scrollTo(0, 0);
}
export default function MainRouter() {
return (
);
}
================================================
FILE: docs/src/js/utils/code.ts
================================================
export const CODE_STRIP_START = "/* strip-start */";
export const CODE_STRIP_END = "/* strip-end */";
/**
* Strip ignored text from example code.
*/
export function stripIgnored(code: string): string {
if (!code.includes(CODE_STRIP_START)) {
return code;
}
const components = code.split(CODE_STRIP_START);
const left = components[0];
const right = components.slice(1).join(CODE_STRIP_START);
return stripIgnored(
left +
right
.split(CODE_STRIP_END)
.slice(1)
.join(CODE_STRIP_END));
}
================================================
FILE: docs/src/style/index.scss
================================================
/* Project Style Entry Point */
/* Global Setup */
@import "./preload";
@import "./layout";
@import "./vars/theme";
@import "material-components-web/material-components-web";
================================================
FILE: docs/src/style/layout.scss
================================================
/* Google web fonts */
:global {
@import url(https://fonts.googleapis.com/css?family=Roboto);
@import url(https://fonts.googleapis.com/css?family=Roboto+Mono);
@import url(https://fonts.googleapis.com/icon?family=Material+Icons);
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, sans-serif;
}
#root {
display: flex;
flex-direction: row;
padding: 0;
margin: 0;
box-sizing: border-box;
}
code,
pre {
font-family: Roboto Mono, monospace;
}
code {
color: #444444;
}
}
================================================
FILE: docs/src/style/preload.scss
================================================
/* Styles for before react */
:global {
#preload-react {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
font-family: "Roboto", "Helvetica", Sans-serif;
font-size: 1.5em;
color: #333333;
display: flex;
/* flex-direction: column;*/
align-items: center;
justify-content: center;
}
}
================================================
FILE: docs/src/style/vars/_theme.scss
================================================
$mdc-theme-primary: #64DD17;
$mdc-theme-accent: #64DD17;
$mdc-theme-background: #fff;
$app-theme-dark-background: #212121;
$app-showcase-background: rgba(0, 0, 0, 0.05);
$app-showcase-padding: 24px;
@import "@material/theme/mdc-theme";
================================================
FILE: docs/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build/",
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"paths": {
"app/*": [
"src/*"
]
},
"lib": [
"dom",
"es2017"
]
},
"include": [
"./src/**/*",
"./typings/**/*.ts"
]
}
================================================
FILE: docs/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {
"destructuring": "all"
}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
"no-string-literal": false,
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: docs/typings/cssmodule.d.ts
================================================
interface CssModule {
[className: string]: string
}
declare module "*.scss" {
const styles: CssModule
export = styles;
}
declare module "*.css" {
const styles: CssModule
export = styles;
}
================================================
FILE: docs/typings/images.d.ts
================================================
declare module "*.jpg" {
const url: string
export = url;
}
declare module "*.png" {
const url: string
export = url;
}
declare module "*.jpeg" {
const url: string
export = url;
}
================================================
FILE: docs/typings/raw-loader.d.ts
================================================
declare module "raw-loader!*" {
const content: string
export = content;
}
================================================
FILE: docs/vendor.js
================================================
require("react");
require("react-dom");
require("codemirror");
require("fbjs");
================================================
FILE: lerna.json
================================================
{
"lerna": "2.0.0-beta.38",
"packages": [
"packages/*",
"docs"
],
"commands": {
"publish": {
"ignore": [
"docs/*",
"*.md",
"src/"
]
}
},
"version": "independent"
}
================================================
FILE: package.json
================================================
{
"name": "react-material-components-web-root",
"author": "Choi Geonu",
"license": "MIT",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"devDependencies": {
"commander": "^2.9.0",
"cross-env": "^4.0.0",
"del": "^2.2.2",
"fs-extra": "^3.0.1",
"jest": "^20.0.4",
"jest-cli": "^20.0.4",
"lerna": "2.0.0-rc.4",
"lodash": "^4.17.4",
"npm-run-all": "^4.0.1",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"clean": "lerna run clean --no-sort --concurrency=10",
"build": "lerna run build --stream --concurrency=5",
"lint": "lerna run lint --no-sort --concurrency=5",
"watch": "lerna run watch --stream --no-sort --concurrency=50",
"test": "lerna run test",
"test:watch": "jest --watchAll"
},
"jest": {
"testRegex": "packages/[^/]+/lib/.*(/__tests__/.*|\\.(test|spec))\\.(js)$",
"moduleFileExtensions": [
"js"
]
}
}
================================================
FILE: packages/base/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/base/package.json
================================================
{
"name": "@react-mdc/base",
"description": "Core library of @react-mdc components",
"version": "0.1.12",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"@types/react-dom": "^15.5.0",
"classnames": "^2.2.5",
"immutable": "^3.8.1",
"lodash.foreach": "^4.5.0",
"react": "^15.4.2",
"react-dom": "^15.5.4",
"remove": "^0.1.5"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"@types/jsdom": "^2.0.30",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"jsdom": "^10.1.0",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/base/src/ClassNameMetaBase.tsx
================================================
/**
* Meta factory component.
*/
import * as React from "react";
import * as classNames from "classnames";
import {
default as MetaBase,
NativeDOMProps,
} from "./MetaBase";
export type ClassNameChildProps = {
className?: string,
};
export type ClassNameProps = {
className?: string,
};
export default abstract class ClassNameMetaBase
extends MetaBase {
protected renderProps(childProps: ChildProps): ChildProps {
return {
...childProps as any,
className: this.renderClassName(childProps),
};
}
protected renderNativeDOMProps(childProps: ChildProps) {
return {};
}
protected renderClassName(childProps: ChildProps): string {
return classNames(
this.renderBaseClassName(childProps),
...this.renderClassValues(childProps),
this.props.className,
childProps.className,
);
}
protected renderBaseClassName(childProps: ChildProps): string | null {
return null;
}
protected renderClassValues(childProps: ChildProps): ClassValue[] {
return [];
}
}
================================================
FILE: packages/base/src/DefaultComponentBase.ts
================================================
/**
* Default component for meta component.
*/
import * as React from "react";
export default abstract class DefaultComponentBase
extends React.Component {
public render() {
const metaProps: Partial = {};
const childProps: Partial = {};
const metaKeys = this.getMetaPropNames();
const meta = this.getMetaComponent();
const defaultComponent = this.getChildComponent();
for (const key in this.props) {
if (!this.props.hasOwnProperty(key)) {
continue;
}
if (metaKeys.indexOf(key as any) !== -1) {
metaProps[key] = this.props[key];
} else {
childProps[key] = this.props[key];
}
}
return React.createElement(
meta,
metaProps as MetaProps,
React.createElement(defaultComponent as any, childProps as ChildProps));
}
protected abstract getMetaPropNames(): string[];
protected abstract getMetaComponent(): React.ComponentClass;
protected abstract getChildComponent(): string | React.ComponentClass | React.SFC;
}
================================================
FILE: packages/base/src/MetaBase.tsx
================================================
/**
* Base class of meta component.
*/
import * as React from "react";
import {
Attributes,
CSSVariables,
default as NativeDOMAdapter,
EventListeners,
} from "./NativeDOMAdapter";
export type Props = {
children: React.ReactElement,
};
export type NativeDOMProps = {
cssVariables?: CSSVariables,
eventListeners?: EventListeners,
attributes?: Attributes,
};
export default abstract class BaseMeta
extends React.Component, State> {
public render() {
const {
children,
} = this.props;
const props = this.renderProps(children.props);
const nativeDOMProps = this.renderNativeDOMProps(children.props);
return (
{React.cloneElement(children, props)}
);
}
protected abstract renderProps(childProps: ChildProps): ChildProps;
protected abstract renderNativeDOMProps(childProps: ChildProps): NativeDOMProps;
}
================================================
FILE: packages/base/src/NativeDOMAdapter.tsx
================================================
import {
OrderedSet,
} from "immutable";
import * as forEach from "lodash.foreach";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { EVENT_MAP } from "./event";
export type EventListeners = {
[eventType: string]: EventListener[],
};
export type CSSVariables = {
[name: string]: string,
};
export type Attributes = {
[name: string]: string,
};
export type Props = {
cssVariables?: CSSVariables,
eventListeners?: EventListeners,
attributes?: Attributes,
children: React.ReactElement,
};
type InternalProps = {
cssVariables: CSSVariables,
eventListeners: EventListeners,
attributes: Attributes,
children: React.ReactElement,
};
/**
* High order components that adds native DOM properties
* that can't be controlled by React.
*/
export default class NativeDOMAdapter extends React.Component {
public static defaultProps = {
cssVariables: {},
eventListeners: {},
attributes: {},
};
public props: Props;
// Last known DOM node
private lastDOMNode: HTMLElement | null;
public render() {
const child = this.props.children;
const reactConvertibles = this.filterReactEventConvertibles(this.props.eventListeners || {});
const merged = this.mergeEvents(reactConvertibles, child.props);
return React.cloneElement(child, {
ref: this.handleRef,
...merged,
});
}
public componentDidUpdate(origPrevProps: Props) {
const props = this.internalProps(this.props);
const prevProps = this.internalProps(origPrevProps);
this.updateNode(this.lastDOMNode, this.lastDOMNode, prevProps, props);
}
private internalProps(props: Props): InternalProps {
return {
cssVariables: (props.cssVariables as CSSVariables),
eventListeners: (props.eventListeners as EventListeners),
attributes: (props.attributes as Attributes),
children: props.children,
};
}
private mergeEvents(eventListeners: EventListeners, props: any) {
const merged = { ...props };
forEach(eventListeners, (listeners: EventListener[], eventType: string) => {
const eventProp = EVENT_MAP[eventType];
if (eventProp == null) {
return;
}
let givenEvent;
if (eventProp in merged) {
givenEvent = merged[eventProp];
} else {
givenEvent = () => { };
}
const listener = (event: React.SyntheticEvent) => {
const nativeEvent = event.nativeEvent;
const proxiedEvent: any = {};
for (const key in nativeEvent) {
if (key.slice(0, 1) !== "_") {
proxiedEvent[key] = nativeEvent[key];
}
}
proxiedEvent.stopPropagation = () => {
event.stopPropagation();
nativeEvent.stopPropagation();
};
givenEvent(event);
if (event.defaultPrevented) {
return;
}
listeners.every((nativeListener) => {
nativeListener(proxiedEvent);
return !nativeEvent.defaultPrevented;
});
};
merged[eventProp] = listener;
});
return merged;
}
// Manage CSS variables
private removeCssVariables(dom: HTMLElement, toRemove: CSSVariables) {
forEach(toRemove, (value: any, key: string) => {
if (dom.style.getPropertyValue(key) === value) {
dom.style.removeProperty(key);
}
});
}
private addCssVariables(dom: HTMLElement, toAdd: CSSVariables) {
forEach(toAdd, (value: any, key: string) => {
if (dom.style.getPropertyValue(key) !== value) {
dom.style.setProperty(key, value);
}
});
}
private updateCssVariables(dom: HTMLElement, prev: CSSVariables, next: CSSVariables) {
const toRemove: CSSVariables = {};
const toAdd: CSSVariables = {};
forEach(prev, (value: any, key: string) => {
if (next[key] !== value) {
toRemove[key] = value;
}
});
forEach(next, (value: any, key: string) => {
if (prev[key] !== value) {
toAdd[key] = value;
}
});
this.removeCssVariables(dom, toRemove);
this.addCssVariables(dom, toAdd);
}
// Manage event listeners
private filterReactEventConvertibles(eventListeners: EventListeners): EventListeners {
const nativeListeners: EventListeners = {};
forEach(eventListeners, (listeners, eventType) => {
if (eventType in EVENT_MAP) {
nativeListeners[eventType] = listeners;
}
});
return nativeListeners;
}
private filterNativeEvents(eventListeners: EventListeners): EventListeners {
const nativeListeners: EventListeners = {};
forEach(eventListeners, (listeners, eventType) => {
if (!(eventType in EVENT_MAP)) {
nativeListeners[eventType] = listeners;
}
});
return nativeListeners;
}
private removeEventListeners(dom: HTMLElement, toRemove: EventListeners) {
forEach(this.filterNativeEvents(toRemove), (listeners: EventListener[], event: string) => {
listeners.forEach((listener) => {
dom.removeEventListener(event, listener);
});
});
}
private addEventListeners(dom: HTMLElement, toAdd: EventListeners) {
forEach(this.filterNativeEvents(toAdd), (listeners: EventListener[], event: string) => {
listeners.forEach((listener) => {
dom.addEventListener(event, listener);
});
});
}
private updateEventListeners(dom: HTMLElement, prev: EventListeners, next: EventListeners) {
const prevKeys = Object.keys(prev);
const nextKeys = Object.keys(next);
const allKeys: string[] = OrderedSet
.of(...nextKeys.concat(prevKeys))
.toJS();
// Find listeners to add / remove with order preservation
type Diff = {
event: string,
toAdd: EventListener[],
toRemove: EventListener[],
};
const diff: Diff[] = allKeys.map((event: string) => {
const prevListeners: EventListener[] = prev[event] || [];
const nextListeners: EventListener[] = next[event] || [];
let diffStart;
for (diffStart = 0; diffStart < Math.min(prevListeners.length, nextListeners.length); diffStart++) {
if (prevListeners[diffStart] !== nextListeners[diffStart]) {
break;
}
}
return {
event,
toRemove: prevListeners.slice(diffStart),
toAdd: nextListeners.slice(diffStart),
};
});
const toAdd: EventListeners = diff.reduce((listeners, item) => ({
...listeners,
[item.event]: item.toAdd,
}), {} as EventListeners);
const toRemove: EventListeners = diff.reduce((listeners, item) => ({
...listeners,
[item.event]: item.toRemove,
}), {} as EventListeners);
this.removeEventListeners(dom, toRemove);
this.addEventListeners(dom, toAdd);
}
// Manage attributes
private removeAttributes(dom: HTMLElement, toRemove: Attributes) {
forEach(toRemove, (value: any, key: string) => {
if (dom.getAttribute(key) === value) {
dom.removeAttribute(key);
}
});
}
private addAttributes(dom: HTMLElement, toAdd: Attributes) {
forEach(toAdd, (value: any, key: string) => {
if (dom.getAttribute(key) !== value) {
dom.setAttribute(key, value);
}
});
}
private updateAttributes(dom: HTMLElement, prev: Attributes, next: Attributes) {
const toRemove: Attributes = {};
const toAdd: Attributes = {};
forEach(prev, (value: string, key: string) => {
if (next[key] !== value) {
toRemove[key] = value;
}
});
forEach(next, (value: string, key: string) => {
if (prev[key] !== value) {
toAdd[key] = value;
}
});
this.removeAttributes(dom, toRemove);
this.addAttributes(dom, toAdd);
}
private updateNode(
prevNode: HTMLElement | null, nextNode: HTMLElement | null,
prevProps: InternalProps, props: InternalProps) {
if (prevNode === nextNode) {
if (nextNode != null) {
// Update
this.updateCssVariables(nextNode, prevProps.cssVariables, props.cssVariables);
this.updateEventListeners(nextNode, prevProps.eventListeners, props.eventListeners);
this.updateAttributes(nextNode, prevProps.attributes, props.attributes);
}
} else {
if (prevNode != null) {
// Remove from previous DOM node
this.removeCssVariables(prevNode, props.cssVariables);
this.removeEventListeners(prevNode, props.eventListeners);
this.removeAttributes(prevNode, props.attributes);
}
if (nextNode != null) {
// Add to new DOM node
this.addCssVariables(nextNode, props.cssVariables);
this.addEventListeners(nextNode, props.eventListeners);
this.addAttributes(nextNode, props.attributes);
}
}
}
private handleRef = (ref: React.ReactInstance | null) => {
const props = this.internalProps(this.props);
const prevNode = this.lastDOMNode;
let nextNode: HTMLElement | null = null;
if (ref != null) {
nextNode = ReactDOM.findDOMNode(ref);
}
this.updateNode(prevNode, nextNode, props, props);
this.lastDOMNode = nextNode;
}
}
================================================
FILE: packages/base/src/__tests__/ClassNameMetaBase.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import ClassNameMetaBase from "../ClassNameMetaBase";
import NativeDOMAdapter from "../NativeDOMAdapter";
describe("ClassNameMetaBase", () => {
it("Should render base classname", () => {
class MetaImpl extends ClassNameMetaBase<{}, {}, {}> {
protected renderBaseClassName() {
return "foo";
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.find("a").hasClass("foo")).toBeTruthy();
});
it("Should render class values", () => {
class MetaImpl extends ClassNameMetaBase<{}, {}, {}> {
protected renderClassValues() {
return [{
foo: true,
bar: false,
baz: undefined,
}, "qux"];
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.find("a").hasClass("foo")).toBeTruthy();
expect(wrapper.find("a").hasClass("bar")).toBeFalsy();
expect(wrapper.find("a").hasClass("baz")).toBeFalsy();
expect(wrapper.find("a").hasClass("qux")).toBeTruthy();
});
it("Should preserve meta's className", () => {
class MetaImpl extends ClassNameMetaBase<{}, { className: string }, {}> {
protected renderBaseClassName() {
return "foo";
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.find("a").hasClass("meta")).toBeTruthy();
expect(wrapper.find("a").hasClass("foo")).toBeTruthy();
});
it("Should preserve child's className", () => {
class MetaImpl extends ClassNameMetaBase<{ className: string }, { className: string }, {}> {
protected renderBaseClassName() {
return "foo";
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.find("a").hasClass("meta")).toBeTruthy();
expect(wrapper.find("a").hasClass("foo")).toBeTruthy();
expect(wrapper.find("a").hasClass("child")).toBeTruthy();
});
});
================================================
FILE: packages/base/src/__tests__/DefaultComponentBase.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import ClassNameMetaBase from "../ClassNameMetaBase";
import DefaultComponentBase from "../DefaultComponentBase";
describe("DefaultComponentBase", () => {
it("Should render default component with meta", () => {
class MetaImpl extends ClassNameMetaBase<{}, { bar?: boolean, className?: string }, {}> {
protected renderBaseClassName() {
return "foo";
}
protected renderClassValues() {
return [{
bar: this.props.bar,
}];
}
}
class Default extends DefaultComponentBase<{}, { bar?: boolean }, {}> {
protected getMetaPropNames() {
return ["bar"];
}
protected getMetaComponent() {
return MetaImpl;
}
protected getChildComponent() {
return "div";
}
}
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
expect(wrapper.find("div").hasClass("foo")).toBeTruthy();
expect(wrapper.find("div").hasClass("bar")).toBeTruthy();
});
});
================================================
FILE: packages/base/src/__tests__/MetaBase.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import MetaBase from "../MetaBase";
import NativeDOMAdapter from "../NativeDOMAdapter";
describe("MetaBase", () => {
it("Should update child's props", () => {
class MetaImpl extends MetaBase<{ href?: string }, { link: string }, {}> {
protected renderProps(childProps: { href: string }) {
return {
href: this.props.link,
};
}
protected renderNativeDOMProps(childProps: { href: string }) {
return {};
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.equals(
,
)).toBeTruthy();
});
it("Should update NativeDOMAdapter's props", () => {
class MetaImpl extends MetaBase<{ href?: string }, { link: string, name: string }, {}> {
protected renderProps(childProps: { href: string }) {
return {
href: this.props.link,
};
}
protected renderNativeDOMProps(childProps: { href: string }) {
return {
attributes: {
"data-sitename": this.props.name,
},
};
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.equals(
,
)).toBeTruthy();
});
it("should overwrite child's props if conflicted", () => {
class MetaImpl extends MetaBase<{ href?: string }, { link: string }, {}> {
protected renderProps(childProps: { href: string }) {
return {
href: this.props.link,
};
}
protected renderNativeDOMProps(childProps: { href: string }) {
return {};
}
}
const wrapper = enzyme.shallow(
,
);
expect(wrapper.equals(
,
)).toBeTruthy();
});
});
================================================
FILE: packages/base/src/__tests__/NativeDOMAdapter.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import NativeDOMAdapter from "../NativeDOMAdapter";
describe("NativeDOMAdapter", () => {
it("Should render native DOM given as child", () => {
const wrapper = enzyme.mount(
,
);
const node = wrapper.getDOMNode();
expect(node.tagName.toLowerCase()).toBe("a");
});
it("Should set attributes to DOM", () => {
const wrapper = enzyme.mount(
,
);
const node = wrapper.getDOMNode();
expect(node.getAttribute("data-link")).toBe("http://www.daum.net");
});
it("Should unset removed attributes to DOM", () => {
const rootNode = document.createElement("div");
const wrapper = enzyme.mount(
,
{ attachTo: rootNode },
);
const node = wrapper.getDOMNode();
expect(node.getAttribute("data-link")).toBe("http://www.daum.net");
expect(node.getAttribute("data-sitename")).toBe("Daum");
// Update
wrapper.setProps({
attributes: {
"data-sitename": "Daum",
},
});
expect(node.hasAttribute("data-link")).toBeFalsy();
expect(node.getAttribute("data-sitename")).toBe("Daum");
});
/*
Many DOM emulators does not support arbitrary CSS property.
So we use known css properties for testing instead of
CSS variables like `--react-mdc-foo`
*/
it("should render css variable into styles", () => {
// Many DOM emulators does not support arbitrary CSS property.
// So we use known css properties for testing instead of
// CSS variables like `--react-mdc-foo`
const wrapper = enzyme.mount(
,
);
const node = wrapper.getDOMNode() as HTMLDivElement;
expect(node.style.getPropertyValue("display")).toBe("123");
});
it("should unset removed css variable from styles", () => {
const wrapper = enzyme.mount(
,
);
const node = wrapper.getDOMNode() as HTMLDivElement;
expect(node.style.getPropertyValue("display")).toBe("123");
expect(node.style.getPropertyValue("padding")).toBe("0px");
// Update
wrapper.setProps({
cssVariables: {
padding: "0px",
},
});
expect(node.style.getPropertyValue("padding")).toBe("0px");
expect(node.style.getPropertyValue("display")).toBe("");
});
it("should add native event listeners by order", (done) => {
let firstCalled = false;
const first = () => {
firstCalled = true;
};
const second = () => {
if (!firstCalled) {
fail("First listener not called!");
} else {
done();
}
};
const wrapper = enzyme.mount(
,
);
const node = wrapper.getDOMNode() as HTMLDivElement;
const event = new MouseEvent("foo");
node.dispatchEvent(event);
});
it("should remove removed native event listeners from node", (done) => {
let firstCalled = false;
let secondCalled = false;
const first = () => {
if (firstCalled) {
fail("First should not be called twice!");
}
firstCalled = true;
};
const second = () => {
if (!firstCalled) {
fail("First listener not called!");
}
if (secondCalled) {
done();
}
secondCalled = true;
};
const wrapper = enzyme.mount(
,
);
const node = wrapper.getDOMNode() as HTMLDivElement;
node.dispatchEvent(new MouseEvent("foo"));
wrapper.setProps({
eventListeners: {
foo: [second],
},
});
node.dispatchEvent(new MouseEvent("foo"));
});
it("should add known react event listeners by order using props", (done) => {
let firstCalled = false;
const first = () => {
firstCalled = true;
};
const second = () => {
if (!firstCalled) {
fail("First listener not called!");
} else {
done();
}
};
const wrapper = enzyme.mount(
,
);
wrapper.simulate("click");
});
it("should remove removed known react event listeners from props", (done) => {
let firstCalled = false;
let secondCalled = false;
const first = () => {
if (firstCalled) {
fail("First should not be called twice!");
}
firstCalled = true;
};
const second = () => {
if (!firstCalled) {
fail("First listener not called!");
}
if (secondCalled) {
done();
}
secondCalled = true;
};
const wrapper = enzyme.mount(
,
);
wrapper.simulate("click");
wrapper.setProps({
eventListeners: {
click: [second],
},
});
wrapper.simulate("click");
});
});
================================================
FILE: packages/base/src/__tests__/util.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import * as util from "../util";
describe("includes()", () => {
it("should check existence of item in array", () => {
const array = [2, 4, 6, 8];
expect(util.includes(array, 2)).toBe(true);
expect(util.includes(array, 3)).toBe(false);
expect(util.includes(array, 2, (left, right) => {
return left % 2 === right % 2;
})).toBe(true);
expect(util.includes(array, 3, (left, right) => {
return left % 2 === right % 2;
})).toBe(false);
});
});
describe("eventHandlerDecorator()", () => {
it("should call wrapper after original handler", (done) => {
let called = false;
function wrapper() {
if (called) {
done();
} else {
fail("Original handler was not called!");
}
}
function handler() {
called = true;
}
const wrapped = util.eventHandlerDecorator(wrapper)(handler);
const button = enzyme.mount(
,
);
if (button == null) {
fail("Button was not rendered!");
return;
}
button.simulate("click");
});
it("should not call wrapper when default is prevented", (done) => {
function wrapper() {
fail("It should not be called!");
}
function handler(e: React.SyntheticEvent) {
e.preventDefault();
}
const wrapped = util.eventHandlerDecorator(wrapper)(handler);
function proxy(e: React.SyntheticEvent) {
// We should call `done()` after handler ends.
// So we proxy the event.
wrapped(e);
done();
}
const button = enzyme.mount(
,
);
if (button == null) {
fail("Button was not rendered!");
return;
}
button.simulate("click");
});
it("should call wrapper even if original handler is a kind of null", (done) => {
function wrapper() {
done();
}
const wrapped = util.eventHandlerDecorator(wrapper)(null);
const button = enzyme.mount(
,
);
if (button == null) {
fail("Button was not rendered!");
return;
}
button.simulate("click");
});
});
================================================
FILE: packages/base/src/event.ts
================================================
export const EVENT_MAP = {
abort: "onAbort",
blur: "onBlur",
canplay: "onCanPlay",
canplaythrough: "onCanPlayThrough",
change: "onChange",
click: "onClick",
contextmenu: "onContextMenu",
copy: "onCopy",
cut: "onCut",
dblclick: "onDoubleClick",
drag: "onDrag",
dragend: "onDragEnd",
dragenter: "onDragEnter",
dragleave: "onDragLeave",
dragover: "onDragOver",
dragstart: "onDragStart",
drop: "onDrop",
durationchange: "onDurationChange",
emptied: "onEmptied",
ended: "onEnded",
error: "onError",
focus: "onFocus",
input: "onInput",
keydown: "onKeyDown",
keypress: "onKeyPress",
keyup: "onKeyUp",
load: "onLoad",
loadeddata: "onLoadedData",
loadedmetadata: "onLoadedMetadata",
loadstart: "onLoadStart",
mousedown: "onMouseDown",
mouseenter: "onMouseEnter",
mouseleave: "onMouseLeave",
mousemove: "onMouseMove",
mouseout: "onMouseOut",
mouseover: "onMouseOver",
mouseup: "onMouseUp",
mousewheel: "onWheel",
paste: "onPaste",
pause: "onPause",
play: "onPlay",
playing: "onPlaying",
progress: "onProgress",
ratechange: "onRateChange",
reset: "onReset",
scroll: "onScroll",
seekend: "onSeeked",
seeking: "onSeeking",
select: "onSelect",
stalled: "onStalled",
submit: "onSubmit",
suspend: "onSuspend",
timeupdate: "onTimeUpdate",
touchcancel: "onTouchCancel",
touchend: "onTouchEnd",
touchmove: "onTouchMove",
touchstart: "onTouchStart",
volumechange: "onVolumeChange",
waiting: "onWaiting",
wheel: "onWheel",
};
/* Not specified
"activate"
"beforeactivate"
"beforecopy"
"beforecut"
"beforedeactivate"
"beforepaste"
"cuechange"
"deactivate"
"invalid"
"selectstart"
"ariarequest"
"command"
"gotpointeCapturer"
"lostpointeCapturer"
"webkitfullscreenchange"
"webkitfullscreenerror"
*/
================================================
FILE: packages/base/src/index.tsx
================================================
import ClassNameMetaBase from "./ClassNameMetaBase";
import DefaultComponentBase from "./DefaultComponentBase";
import MetaBase from "./MetaBase";
import NativeDOMAdapter from "./NativeDOMAdapter";
export {
ClassNameMetaBase,
DefaultComponentBase,
MetaBase,
NativeDOMAdapter,
};
export default {
ClassNameMetaBase,
DefaultComponentBase,
MetaBase,
NativeDOMAdapter,
};
================================================
FILE: packages/base/src/types.tsx
================================================
import * as React from "react";
export type Config = {};
export type ReactComponent
= React.ComponentClass
| React.StatelessComponent
;
export type Wrappable
= ReactComponent
| React.ReactElement
;
================================================
FILE: packages/base/src/util.ts
================================================
/**
* Common utilities
*/
import * as React from "react";
/**
* Decorate event handler function with default handler.
* Default handler will not be called when decorated handler prevents default.
*
* Example:
*
* let {onClick} = this.props;
* onClick = eventHandlerDecorator(this.handleClick)(onClick);
*
* @param handler a default event handler.
*
* @return event handler decorator with default functionally.
* decorated function can be null
*/
export function eventHandlerDecorator(defaultHandler: React.ReactEventHandler):
(target: React.ReactEventHandler | null) => React.ReactEventHandler {
return (handler: React.ReactEventHandler | null)
: React.ReactEventHandler => (evt: React.SyntheticEvent) => {
if (handler != null) {
handler(evt);
}
if (!evt.isDefaultPrevented()) {
defaultHandler(evt);
}
};
}
/**
* Array inclusion tester
*/
export function includes(array: T[], item: T, predicate: (left, right) => boolean = (x, y) => x === y): boolean {
// tslint:disable:prefer-for-of
for (let i = 0; i < array.length; i++) {
const itemAtIndex = array[i];
if (predicate(item, itemAtIndex)) {
return true;
}
}
return false;
}
================================================
FILE: packages/base/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/base/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/button/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/button/package.json
================================================
{
"name": "@react-mdc/button",
"description": "React wrapper of @material/button",
"version": "0.1.8",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/button": "^0.3.4",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/button/src/Button.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export const propertyClassNames = {
PREFIX: CLASS_NAME,
DENSE: `${CLASS_NAME}--dense`,
RAISED: `${CLASS_NAME}--raised`,
COMPACT: `${CLASS_NAME}--compact`,
PRIMARY: `${CLASS_NAME}--primary`,
ACCENT: `${CLASS_NAME}--accent`,
};
export type MetaProps = {
dense?: boolean,
raised?: boolean,
compact?: boolean,
primary?: boolean,
accent?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues(_childProps: ChildProps) {
return [{
[propertyClassNames.DENSE]: this.props.dense,
[propertyClassNames.RAISED]: this.props.raised,
[propertyClassNames.COMPACT]: this.props.compact,
[propertyClassNames.PRIMARY]: this.props.primary,
[propertyClassNames.ACCENT]: this.props.accent,
}];
}
}
export default class Button extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"dense",
"raised",
"compact",
"primary",
"accent",
];
}
protected getChildComponent() {
return "button";
}
}
================================================
FILE: packages/button/src/__tests__/Button.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Button from "../Button";
describe("Button", () => {
it("Should have mdc-button classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
});
it("Should have button element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("button").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--dense")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--raised")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--compact")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--primary")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--accent")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-button")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--dense")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--raised")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--compact")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--primary")).toBeTruthy();
expect(wrapper.hasClass("mdc-button--accent")).toBeTruthy();
});
});
================================================
FILE: packages/button/src/constants.tsx
================================================
export const BASE_CLASS_NAME = "mdc-button";
================================================
FILE: packages/button/src/index.tsx
================================================
export { default } from "./Button";
================================================
FILE: packages/button/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/button/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/card/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/card/package.json
================================================
{
"name": "@react-mdc/card",
"description": "React wrapper of @material/card",
"version": "0.1.9",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/card": "^0.1.2",
"@react-mdc/base": "^0.1.12",
"@react-mdc/button": "^0.1.8",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/card/src/Action.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import Button from "@react-mdc/button";
import {
MetaProps as ButtonMetaProps,
} from "@react-mdc/button/lib/Button";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__action`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
// Button with compact as default
function CompactButton(props: React.HTMLProps & ButtonMetaProps) {
return React.createElement(Button, {
compact: true,
...props,
});
}
export default class Action
extends DefaultComponentBase & ButtonMetaProps, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent(): React.SFC & ButtonMetaProps> {
return CompactButton;
}
}
================================================
FILE: packages/card/src/Actions.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__actions`;
export const propertyClassNames = {
VERTICAL: `${CLASS_NAME}--vertical`,
};
export type MetaProps = {
vertical?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.VERTICAL]: this.props.vertical,
}];
}
}
export default class Actions extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"vertical",
];
}
protected getChildComponent() {
return "section";
}
}
================================================
FILE: packages/card/src/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export const propertyClassNames = {
DARK: `${CLASS_NAME}--theme-dark`,
};
export type MetaProps = {
dark?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Card component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.DARK]: this.props.dark,
}];
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"dark",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/card/src/HorizontalBlock.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__horizontal-block`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Horizontal block section component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export type Props = React.HTMLProps & MetaProps;
export default class HorizontalBlock extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/card/src/Media.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__media`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Media section component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Media extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "section";
}
}
================================================
FILE: packages/card/src/MediaItem.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__media-item`;
export type Size = 1.5 | 2 | 3;
export function classNameForSize(size: Size): string {
switch (size) {
case 1.5:
return `${CLASS_NAME}--1dot5x`;
case 2:
return `${CLASS_NAME}--2x`;
case 3:
return `${CLASS_NAME}--3x`;
default:
throw new TypeError(`Invalid size: ${size}`);
}
}
export type MetaProps = {
size?: Size,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Media item component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [
this.props.size == null ? null : classNameForSize(this.props.size),
];
}
}
export default class MediaItem extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"size",
];
}
protected getChildComponent() {
return "img";
}
}
================================================
FILE: packages/card/src/Primary.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__primary`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Primary section component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Primary extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "section";
}
}
================================================
FILE: packages/card/src/Subtitle.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__subtitle`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Primary section subtitle component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Subtitle extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "h2";
}
}
================================================
FILE: packages/card/src/SupportingText.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__supporting-text`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Supporting text section component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class SupportingText extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "section";
}
}
================================================
FILE: packages/card/src/Title.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__title`;
export const propertyClassNames = {
LARGE: `${CLASS_NAME}--large`,
};
export type MetaProps = {
large?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Primary section title component
*/
export class Meta extends ClassNameMetaBase {
public static defaultProps = {
large: false,
};
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.LARGE]: this.props.large,
}];
}
}
export default class Title extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"large",
];
}
protected getChildComponent() {
return "h1";
}
}
================================================
FILE: packages/card/src/__tests__/Action.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Action from "../Action";
describe("Action", () => {
it("Should have mdc-card__action classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__action")).toBeTruthy();
});
it("Should have button element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("button").exists()).toBeTruthy();
});
});
================================================
FILE: packages/card/src/__tests__/Actions.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Actions from "../Actions";
describe("Actions", () => {
it("Should have mdc-card__actions classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__actions")).toBeTruthy();
});
it("Should have section element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("section").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__actions")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__actions--vertical")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__actions")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__actions--vertical")).toBeFalsy();
});
});
================================================
FILE: packages/card/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-card classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card")).toBeTruthy();
expect(wrapper.hasClass("mdc-card--theme-dark")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card")).toBeTruthy();
expect(wrapper.hasClass("mdc-card--theme-dark")).toBeFalsy();
});
});
================================================
FILE: packages/card/src/__tests__/HorizontalBlock.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import HorizontalBlock from "../HorizontalBlock";
describe("HorizontalBlock", () => {
it("Should have mdc-card__horizontal-block classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__horizontal-block")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/card/src/__tests__/Media.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Media from "../Media";
describe("Media", () => {
it("Should have mdc-card__media classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__media")).toBeTruthy();
});
it("Should have section element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("section").exists()).toBeTruthy();
});
});
================================================
FILE: packages/card/src/__tests__/MediaItem.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import MediaItem from "../MediaItem";
describe("MediaItem", () => {
it("Should have mdc-card__media-item classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__media-item")).toBeTruthy();
});
it("Should have img element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("img").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__media-item")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__media-item--1dot5x")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__media-item")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__media-item--2x")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__media-item")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__media-item--3x")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__media-item")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__media-item--1dot5x")).toBeFalsy();
expect(wrapper.hasClass("mdc-card__media-item--2x")).toBeFalsy();
expect(wrapper.hasClass("mdc-card__media-item--3x")).toBeFalsy();
});
});
================================================
FILE: packages/card/src/__tests__/Primary.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Primary from "../Primary";
describe("Primary", () => {
it("Should have mdc-card__primary classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__primary")).toBeTruthy();
});
it("Should have section element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("section").exists()).toBeTruthy();
});
});
================================================
FILE: packages/card/src/__tests__/Subtitle.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Subtitle from "../Subtitle";
describe("Subtitle", () => {
it("Should have mdc-card__subtitle classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__subtitle")).toBeTruthy();
});
it("Should have h2 element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("h2").exists()).toBeTruthy();
});
});
================================================
FILE: packages/card/src/__tests__/SupportingText.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import SupportingText from "../SupportingText";
describe("SupportingText", () => {
it("Should have mdc-card__supporting-text classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__supporting-text")).toBeTruthy();
});
it("Should have section element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("section").exists()).toBeTruthy();
});
});
================================================
FILE: packages/card/src/__tests__/Title.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Title from "../Title";
describe("Title", () => {
it("Should have mdc-card__title classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__title")).toBeTruthy();
});
it("Should have h1 element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("h1").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__title")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__title--large")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-card__title")).toBeTruthy();
expect(wrapper.hasClass("mdc-card__title--large")).toBeFalsy();
});
});
================================================
FILE: packages/card/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-card";
================================================
FILE: packages/card/src/index.ts
================================================
import Action from "./Action";
import Actions from "./Actions";
import Container from "./Container";
import HorizontalBlock from "./HorizontalBlock";
import Media from "./Media";
import MediaItem from "./MediaItem";
import Primary from "./Primary";
import Subtitle from "./Subtitle";
import SupportingText from "./SupportingText";
import Title from "./Title";
export default class Card extends Container {
public static Action = Action;
public static Actions = Actions;
public static HorizontalBlock = HorizontalBlock;
public static Media = Media;
public static MediaItem = MediaItem;
public static Primary = Primary;
public static Subtitle = Subtitle;
public static SupportingText = SupportingText;
public static Title = Title;
}
================================================
FILE: packages/card/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/card/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/checkbox/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/checkbox/package.json
================================================
{
"name": "@react-mdc/checkbox",
"description": "React wrapper of @material/checkbox",
"version": "0.1.12",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/checkbox": "^0.3.2",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/prop-types": "^15.5.1",
"@types/react": "^15.0.18",
"@types/react-dom": "^0.14.23",
"classnames": "^2.2.5",
"immutable": "^3.8.1",
"prop-types": "^15.5.8",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/checkbox/src/Background.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__background`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Background extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/checkbox/src/Checkmark.tsx
================================================
import * as React from "react";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__checkmark`;
export const PATH_CLASS_NAME = `${CLASS_NAME}__path`;
/**
* Checkbox checkmark svg component
*/
export default class Checkmark extends React.Component<{}, {}> {
public render() {
return (
);
}
}
================================================
FILE: packages/checkbox/src/Container.tsx
================================================
import * as React from "react";
import { getCorrectEventName } from "@material/animation/dist/mdc.animation";
import { MDCCheckboxFoundation } from "@material/checkbox/dist/mdc.checkbox";
import {
Map,
OrderedSet,
Set,
} from "immutable";
import * as PropTypes from "prop-types";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { ContainerAdapter, FoundationAdapter } from "./adapter";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
checked?: boolean,
disabled?: boolean,
indeterminate?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
export type State = {
foundationClasses: Set,
foundationEventListeners: Map>,
};
export type ChildContext = {
adapter: FoundationAdapter,
};
/**
* Checkbox input container component
*/
export class Meta extends ClassNameMetaBase {
public static childContextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter),
};
public state: State = {
foundationClasses: OrderedSet(),
foundationEventListeners: Map>(),
};
private adapter: FoundationAdapter;
private foundation: MDCCheckboxFoundation;
constructor(props) {
super(props);
this.adapter = new FoundationAdapter();
this.foundation = new MDCCheckboxFoundation(this.adapter.toObject());
}
public getChildContext(): ChildContext {
return {
adapter: this.adapter,
};
}
// Foundation lifecycle
public componentDidMount() {
this.adapter.setContainerAdapter(new ContainerAdapterImpl(this));
this.foundation.init();
this.adapter.setDefaultOnChangeHandler(this.handleChange);
if (this.props.checked != null) {
this.foundation.setChecked(this.props.checked);
}
if (this.props.disabled != null) {
this.foundation.setDisabled(this.props.disabled);
}
}
public componentWillUnmount() {
this.foundation.destroy();
this.adapter.setContainerAdapter(new ContainerAdapter());
}
// Sync props and internal state
public componentWillReceiveProps(props: MetaProps) {
this.syncFoundation(props);
}
protected renderNativeDOMProps() {
return {
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [
this.state.foundationClasses.toJS(),
];
}
private syncFoundation(props: MetaProps) {
if (props.checked != null && this.foundation.isChecked() !== this.props.checked) {
this.foundation.setChecked(props.checked);
}
if (props.disabled != null && this.foundation.isDisabled() !== this.props.disabled) {
this.foundation.setDisabled(props.disabled);
}
if (props.indeterminate != null && this.foundation.isIndeterminate() !== this.props.indeterminate) {
this.foundation.setIndeterminate(props.indeterminate);
}
}
// Event handler
private handleChange: React.ChangeEventHandler = (_evt: React.ChangeEvent) => {
if (this.props.checked != null) {
if (this.foundation.isChecked() !== this.props.checked) {
// Checked state should not be changed by foundation
this.foundation.setChecked(this.props.checked);
}
}
}
}
class ContainerAdapterImpl extends ContainerAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public addClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.add(className),
}));
}
public removeClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.remove(className),
}));
}
public registerAnimationEndHandler(handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
getCorrectEventName(window, "animationend"),
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterAnimationEndHandler(handler: EventListener) {
const evt = getCorrectEventName(window, "animationend");
if (this.element.state.foundationEventListeners.get(evt).includes(handler)) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.delete(evt),
}));
}
}
public forceLayout() {
/* no-op */
}
public isAttachedToDOM(): boolean {
// Always true. Because we initialize foundation on componentDidMount
return true;
}
public isChecked(): boolean | null {
return this.element.props.checked || null;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"checked",
"disabled",
"indeterminate",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/checkbox/src/Default.tsx
================================================
import * as React from "react";
import Background from "./Background";
import Checkmark from "./Checkmark";
import Container from "./Container";
import Mixedmark from "./Mixedmark";
import NativeControl from "./NativeControl";
export type Props = {
inputId?: string,
name?: string,
value?: any,
onChange?: React.FormEventHandler,
checked?: boolean,
disabled?: boolean,
indeterminate?: boolean,
defaultChecked?: boolean,
};
/**
* Checkbox default composed component
*/
export default class Default extends React.Component {
public render() {
const {
inputId,
name,
value,
onChange,
checked,
disabled,
indeterminate,
defaultChecked,
children: _children, // Ignore children
...props,
} = this.props;
return (
);
}
}
================================================
FILE: packages/checkbox/src/Mixedmark.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__mixedmark`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Mixedmark extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/checkbox/src/NativeControl.tsx
================================================
import * as PropTypes from "prop-types";
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
Map,
OrderedSet,
Set,
} from "immutable";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { eventHandlerDecorator } from "@react-mdc/base/lib/util";
import { FoundationAdapter, NativeControlAdapter } from "./adapter";
import { BASE_CLASS_NAME } from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__native-control`;
export type MetaProps = {
onChange?: React.FormEventHandler,
className?: string,
};
export type ChildProps = {
className?: string,
checked?: boolean,
};
export type State = {
foundationEventListeners: Map>,
};
export type Context = {
adapter: FoundationAdapter,
};
/**
* Checkbox input component
*/
export class Meta extends ClassNameMetaBase {
public static contextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter).isRequired,
};
public context: Context;
public state = {
foundationEventListeners: Map>(),
};
public defaultOnChange: React.ChangeEventHandler = () => { };
public componentDidMount() {
this.context.adapter.setNativeControlAdapter(new NativeControlAdapterImpl(this));
}
public componentWillUnmount() {
this.context.adapter.setNativeControlAdapter(new NativeControlAdapter());
}
protected renderNativeDOMProps() {
return {
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderProps(childProps: ChildProps) {
const {
onChange,
} = this.props;
return {
...super.renderProps(childProps),
onChange: (eventHandlerDecorator(this.handleChange)(onChange || null) as React.ChangeEventHandler),
checked: this.context.adapter.isChecked() || undefined,
};
}
private handleChange = (evt: React.ChangeEvent) => {
this.defaultOnChange(evt);
}
}
class NativeControlAdapterImpl extends NativeControlAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public registerChangeHandler(handler: EventListener) {
this.element.setState((state: State) => ({
foundationEventListeners: state.foundationEventListeners.update(
"change",
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterChangeHandler(handler: EventListener) {
this.element.setState((state: State) => ({
foundationEventListeners: state.foundationEventListeners.update(
"change",
OrderedSet(),
(x) => x.delete(handler),
),
}));
}
public getNativeControl(): Element | null {
return ReactDOM.findDOMNode(this.element);
}
public setDefaultOnChangeHandler(handler: React.ChangeEventHandler) {
this.element.defaultOnChange = handler;
}
}
// Input with type="checkbox" as default
function CheckboxInput(props: React.HTMLProps) {
return (
);
}
export type Props = React.HTMLProps & MetaProps;
export default class NativeControl extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"onChange",
];
}
protected getChildComponent(): React.SFC> {
return CheckboxInput;
}
}
================================================
FILE: packages/checkbox/src/__tests__/Background.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Background from "../Background";
describe("Background", () => {
it("Should have mdc-checkbox__background classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-checkbox__background")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/checkbox/src/__tests__/Checkmark.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Checkmark from "../Checkmark";
describe("Checkmark", () => {
it("Should render checkmark SVG", () => {
const wrapper = enzyme.shallow();
expect(wrapper.find("svg").exists()).toBeTruthy();
});
});
================================================
FILE: packages/checkbox/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-checkbox classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-checkbox")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/checkbox/src/__tests__/Default.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Background from "../Background";
import Checkmark from "../Checkmark";
import Container from "../Container";
import Default from "../Default";
import Mixedmark from "../Mixedmark";
import NativeControl from "../NativeControl";
describe("Default", () => {
it("Should render common component composition", () => {
const wrapper = enzyme.shallow();
expect(wrapper.find(Background).exists()).toBeTruthy();
expect(wrapper.find(Checkmark).exists()).toBeTruthy();
expect(wrapper.find(Container).exists()).toBeTruthy();
expect(wrapper.find(Mixedmark).exists()).toBeTruthy();
expect(wrapper.find(NativeControl).exists()).toBeTruthy();
});
});
================================================
FILE: packages/checkbox/src/__tests__/Mixedmark.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Mixedmark from "../Mixedmark";
describe("Mixedmark", () => {
it("Should have mdc-checkbox__mixedmark classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-checkbox__mixedmark")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/checkbox/src/__tests__/NativeControl.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import * as PropTypes from "prop-types";
import { FoundationAdapter } from "../adapter";
import NativeControl from "../NativeControl";
describe("NativeControl", () => {
it("Should have mdc-checkbox__native-control classname", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.hasClass("mdc-checkbox__native-control")).toBeTruthy();
});
it("Should have input element as default component", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.find("input").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/checkbox/src/__tests__/adapter.spec.ts
================================================
import "jest";
import {
ContainerAdapter,
FoundationAdapter,
NativeControlAdapter,
} from "../adapter";
describe("ContainerAdapter", () => {
// TODO: Add adapter tests
it("Should have at least one test", () => {
});
});
describe("FoundationAdapter", () => {
// TODO: Add adapter tests
});
describe("NativeControlAdapter", () => {
// TODO: Add adapter tests
});
================================================
FILE: packages/checkbox/src/adapter.ts
================================================
/**
* Foundation adapters.
*/
/**
* Container adapter interface
* Default implementations are noop and returns meaningless value.
*/
export class ContainerAdapter {
public addClass(_className: string) {
}
public removeClass(_className: string) {
}
public registerAnimationEndHandler(_handler: EventListener) {
}
public deregisterAnimationEndHandler(_handler: EventListener) {
}
public forceLayout() {
}
public isAttachedToDOM(): boolean {
return false;
}
public isChecked(): boolean | null {
return null;
}
}
/**
* Native control adapter interface
* Default implementations are noop and returns meaningless value.
*/
export class NativeControlAdapter {
public registerChangeHandler(_handler: EventListener) {
}
public deregisterChangeHandler(_handler: EventListener) {
}
public getNativeControl(): Element| null {
return null;
}
public setDefaultOnChangeHandler(_onChange: React.ChangeEventHandler) {
}
}
/**
* Composite adapter for MDCChcekboxFoundation
*/
export class FoundationAdapter {
private containerAdapter: ContainerAdapter;
private nativeControlAdapter: NativeControlAdapter;
private defaultOnchangeHandler: React.ChangeEventHandler;
constructor() {
this.containerAdapter = new ContainerAdapter();
this.nativeControlAdapter = new NativeControlAdapter();
this.defaultOnchangeHandler = () => {};
}
public setContainerAdapter(containerAdapter: ContainerAdapter) {
this.containerAdapter = containerAdapter;
}
public setNativeControlAdapter(nativeControlAdapter: NativeControlAdapter) {
this.nativeControlAdapter = nativeControlAdapter;
this.nativeControlAdapter.setDefaultOnChangeHandler(this.defaultOnchangeHandler);
}
public addClass(className: string) {
this.containerAdapter.addClass(className);
}
public removeClass(className: string) {
this.containerAdapter.removeClass(className);
}
public registerAnimationEndHandler(handler: EventListener) {
this.containerAdapter.registerAnimationEndHandler(handler);
}
public deregisterAnimationEndHandler(handler: EventListener) {
this.containerAdapter.deregisterAnimationEndHandler(handler);
}
public registerChangeHandler(handler: EventListener) {
this.nativeControlAdapter.registerChangeHandler(handler);
}
public deregisterChangeHandler(handler: EventListener) {
this.nativeControlAdapter.deregisterChangeHandler(handler);
}
public getNativeControl(): Element | null {
return this.nativeControlAdapter.getNativeControl();
}
public forceLayout() {
this.containerAdapter.forceLayout();
}
public isAttachedToDOM(): boolean {
return this.containerAdapter.isAttachedToDOM();
}
public setDefaultOnChangeHandler(onChange: React.ChangeEventHandler) {
this.nativeControlAdapter.setDefaultOnChangeHandler(onChange);
}
public isChecked(): boolean | null {
return this.containerAdapter.isChecked();
}
/**
* MDCFoundation accepts only object as adapter
* So we create object-proxy of instance.
*/
public toObject(): {} {
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const object = {};
keys.forEach((key: string) => {
object[key] = (...args) => this[key](...args);
});
return object;
}
}
================================================
FILE: packages/checkbox/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-checkbox";
================================================
FILE: packages/checkbox/src/index.ts
================================================
import Background from "./Background";
import Checkmark from "./Checkmark";
import Container from "./Container";
import Default from "./Default";
import Mixedmark from "./Mixedmark";
import NativeControl from "./NativeControl";
export default class Checkbox extends Container {
public static Background = Background;
public static Checkmark = Checkmark;
public static Container = Container;
public static Default = Default;
public static Mixedmark = Mixedmark;
public static NativeControl = NativeControl;
}
================================================
FILE: packages/checkbox/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/checkbox/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/dialog/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/dialog/package.json
================================================
{
"name": "@react-mdc/dialog",
"description": "React wrapper of @material/dialog",
"version": "0.1.7",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/dialog": "^0.2.1",
"@react-mdc/base": "^0.1.12",
"@react-mdc/button": "^0.1.8",
"@types/classnames": "0.0.32",
"@types/prop-types": "^15.5.1",
"@types/react": "^15.0.18",
"@types/react-dom": "^0.14.23",
"classnames": "^2.2.5",
"immutable": "^3.8.1",
"prop-types": "^15.5.8",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/dialog/src/Backdrop.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__backdrop`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Backdrop component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Backdrop extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/dialog/src/Body.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__body`;
export type MetaProps = {
scrollable?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
export const propertyClassNames = {
SCROLLABLE: `${CLASS_NAME}--scrollable`,
};
/**
* Header title component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.SCROLLABLE]: this.props.scrollable,
}];
}
}
export default class Body extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"scrollable",
];
}
protected getChildComponent() {
return "section";
}
}
================================================
FILE: packages/dialog/src/Container.tsx
================================================
import * as React from "react";
import * as classNames from "classnames";
import * as PropTypes from "prop-types";
import { MDCDialogFoundation } from "@material/dialog/dist/mdc.dialog";
import {
Map,
OrderedSet,
Set,
} from "immutable";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { includes } from "@react-mdc/base/lib/util";
import {
BASE_CLASS_NAME,
} from "./constants";
import { ContainerAdapter, FoundationAdapter } from "./adapter";
const {
cssClasses: {
OPEN: OPEN_CLASS_NAME,
},
} = MDCDialogFoundation;
export const CLASS_NAME = BASE_CLASS_NAME;
export const propertyClassNames = {
DARK: `${CLASS_NAME}--theme-dark`,
};
export type MetaProps = {
dark?: boolean,
open?: boolean,
onAccept?: (element: Meta) => void,
onOpen?: (element: Meta) => void,
onCancel?: (element: Meta) => void,
onClose?: (element: Meta) => void,
className?: string,
};
export type ChildProps = {
className?: string,
};
export type State = {
foundationClasses: Set,
foundationEventListeners: Map>,
foundationAttributes: Map,
open: boolean,
};
export type ChildContext = {
adapter: FoundationAdapter,
};
/**
* Dialog component
*/
export class Meta extends ClassNameMetaBase {
public static displayName = "Container";
public static childContextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter),
};
public state: State = {
foundationClasses: OrderedSet(),
foundationAttributes: Map(),
foundationEventListeners: Map>(),
open: false,
};
private adapter: FoundationAdapter;
private foundation: MDCDialogFoundation;
constructor(props) {
super(props);
this.adapter = new FoundationAdapter();
this.foundation = new MDCDialogFoundation(this.adapter.toObject());
}
public getChildContext(): ChildContext {
return {
adapter: this.adapter,
};
}
// Sync props and internal state
public componentWillReceiveProps(props: MetaProps) {
if ((!!props.open) !== (!!this.state.open)) {
if (props.open) {
this.foundation.open();
} else {
this.foundation.close();
}
}
}
// Foundation lifecycle
public componentDidMount() {
this.adapter.setContainerAdapter(new ContainerAdapterImpl(this));
this.foundation.init();
}
public componentWillUnmount() {
this.foundation.destroy();
this.adapter.setContainerAdapter(new ContainerAdapter());
}
/* Public APIs */
public accept(notifyChange: boolean = false) {
this.foundation.accept(notifyChange);
}
public cancel(notifyChange: boolean = false) {
this.foundation.cancel(notifyChange);
}
public getClassName(props: MetaProps, state: State): string {
return classNames(
CLASS_NAME,
{
[propertyClassNames.DARK]: this.props.dark,
},
state.foundationClasses.toJS(),
);
}
protected renderNativeDOMProps() {
return {
attributes: this.state.foundationAttributes.toJS(),
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderClassValues() {
return [this.getClassName(this.props, this.state)];
}
}
class ContainerAdapterImpl extends ContainerAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public hasClass(className: string): boolean {
return includes(
this.element.getClassName(this.element.props, this.element.state).split(/\s+/),
className,
);
}
public addClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.add(className),
}));
// MDCDialog does not provide opening/closing event.
// But we can assume open/close by adding/removing OPEN_CLASS_NAME
if (className === OPEN_CLASS_NAME) {
this.element.setState({
open: true,
});
if (this.element.props.onOpen) {
this.element.props.onOpen(this.element);
}
}
}
public removeClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.remove(className),
}));
// MDCDialog does not provide opening/closing event.
// But we can assume open/close by adding/removing OPEN_CLASS_NAME
if (className === OPEN_CLASS_NAME) {
this.element.setState({
open: false,
});
if (this.element.props.onClose) {
this.element.props.onClose(this.element);
}
}
}
public setAttr(attr: string, val: string) {
this.element.setState((state) => ({
foundationAttributes: state.foundationAttributes.set(attr, val),
}));
}
public registerInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
evt,
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
evt,
OrderedSet(),
(x) => x.delete(handler),
),
}));
}
public notifyAccept() {
if (this.element.props.onAccept != null) {
this.element.props.onAccept(this.element);
}
}
public notifyCancel() {
if (this.element.props.onCancel != null) {
this.element.props.onCancel(this.element);
}
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"dark",
"open",
"onAccept",
"onCancel",
"onOpen",
"onClose",
];
}
protected getChildComponent() {
return "aside";
}
}
================================================
FILE: packages/dialog/src/Footer/Button.tsx
================================================
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import MDCButton from "@react-mdc/button";
import { MetaProps as MDCButtonMetaProps } from "@react-mdc/button/lib/Button";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__button`;
export type MetaProps = {
type: "accept" | "cancel",
className?: string,
};
export type ChildProps = {
className?: string,
};
export const propertyClassNames = {
TYPE_ACCEPT: `${CLASS_NAME}--accept`,
TYPE_CANCEL: `${CLASS_NAME}--cancel`,
};
/**
* Button component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.TYPE_ACCEPT]: this.props.type === "accept",
[propertyClassNames.TYPE_CANCEL]: this.props.type === "cancel",
}];
}
}
export default class Button
extends DefaultComponentBase & MDCButtonMetaProps, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"type",
"className",
];
}
protected getChildComponent() {
return MDCButton;
}
}
================================================
FILE: packages/dialog/src/Footer/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Footer component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export type Props = React.HTMLProps & MetaProps;
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "footer";
}
}
================================================
FILE: packages/dialog/src/Footer/__tests__/Button.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Button from "../Button";
describe("Button", () => {
it("Should have mdc-dialog__footer__button classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__footer__button")).toBeTruthy();
});
it("Should have button element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("button").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__footer__button")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog__footer__button--accept")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog__footer__button--cancel")).toBeFalsy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__footer__button")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog__footer__button--accept")).toBeFalsy();
expect(wrapper.hasClass("mdc-dialog__footer__button--cancel")).toBeTruthy();
});
});
================================================
FILE: packages/dialog/src/Footer/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-dialog__footer classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__footer")).toBeTruthy();
});
it("Should have footer element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("footer").exists()).toBeTruthy();
});
});
================================================
FILE: packages/dialog/src/Footer/constants.ts
================================================
import { BASE_CLASS_NAME as PARENT_BASE_CLASS_NAME } from "../constants";
export const BASE_CLASS_NAME = `${PARENT_BASE_CLASS_NAME}__footer`;
================================================
FILE: packages/dialog/src/Footer/index.ts
================================================
import Button from "./Button";
import Container from "./Container";
export default class Footer extends Container {
public static Button = Button;
}
================================================
FILE: packages/dialog/src/Header/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Header component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "header";
}
}
================================================
FILE: packages/dialog/src/Header/Title.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__title`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Header title component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Title extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "h2";
}
}
================================================
FILE: packages/dialog/src/Header/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-dialog__header classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__header")).toBeTruthy();
});
it("Should have header element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("header").exists()).toBeTruthy();
});
});
================================================
FILE: packages/dialog/src/Header/__tests__/Title.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Title from "../Title";
describe("Title", () => {
it("Should have mdc-dialog__header__title classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__header__title")).toBeTruthy();
});
it("Should have h2 element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("h2").exists()).toBeTruthy();
});
});
================================================
FILE: packages/dialog/src/Header/constants.ts
================================================
import { BASE_CLASS_NAME as PARENT_BASE_CLASS_NAME } from "../constants";
export const BASE_CLASS_NAME = `${PARENT_BASE_CLASS_NAME}__header`;
================================================
FILE: packages/dialog/src/Header/index.ts
================================================
import Container from "./Container";
import Title from "./Title";
export default class Header extends Container {
public static Title = Title;
}
================================================
FILE: packages/dialog/src/Surface.tsx
================================================
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
Map,
OrderedSet,
Set,
} from "immutable";
import * as PropTypes from "prop-types";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
MDCDialogFoundation,
} from "@material/dialog/dist/mdc.dialog";
import { FoundationAdapter, SurfaceAdapter } from "./adapter";
import {
BASE_CLASS_NAME,
} from "./constants";
const {
strings: { FOCUSABLE_ELEMENTS },
} = MDCDialogFoundation;
export const CLASS_NAME = `${BASE_CLASS_NAME}__surface`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export type State = {
foundationClasses: Set,
foundationEventListeners: Map>,
};
export type Context = {
adapter: FoundationAdapter,
};
/**
* Surface component
*/
export class Meta extends ClassNameMetaBase {
public static contextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter).isRequired,
};
public context: Context;
public state: State = {
foundationClasses: OrderedSet(),
foundationEventListeners: Map>(),
};
public componentDidMount() {
this.context.adapter.setSurfaceAdapter(new SurfaceAdapterImpl(this));
}
public componentWillUnmount() {
this.context.adapter.setSurfaceAdapter(new SurfaceAdapter());
}
protected renderNativeDOMProps() {
return {
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderBaseClassName() {
return CLASS_NAME;
}
}
class SurfaceAdapterImpl extends SurfaceAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public registerSurfaceInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
evt,
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterSurfaceInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
evt,
OrderedSet(),
(x) => x.delete(handler),
),
}));
}
public numFocusableTargets(): number {
return this.getDOMNode().querySelectorAll(FOCUSABLE_ELEMENTS)[0].length;
}
public setDialogFocusFirstTarget() {
return this.getDOMNode().querySelectorAll(FOCUSABLE_ELEMENTS)[0].focus();
}
public setInitialFocus() {
}
public getFocusableElements(): Element[] {
return this.getDOMNode().querySelectorAll(FOCUSABLE_ELEMENTS);
}
public getDOMNode(): Element {
return ReactDOM.findDOMNode(this.element);
}
}
export default class Surface extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/dialog/src/__tests__/Backdrop.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Backdrop from "../Backdrop";
describe("Backdrop", () => {
it("Should have mdc-dialog__backdrop classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__backdrop")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/dialog/src/__tests__/Body.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Body from "../Body";
describe("Body", () => {
it("Should have mdc-dialog__body classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__body")).toBeTruthy();
});
it("Should have section element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("section").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__body")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog__body--scrollable")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog__body")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog__body--scrollable")).toBeFalsy();
});
});
================================================
FILE: packages/dialog/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-dialog classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog")).toBeTruthy();
});
it("Should have aside element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("aside").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog--theme-dark")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-dialog")).toBeTruthy();
expect(wrapper.hasClass("mdc-dialog--theme-dark")).toBeFalsy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/dialog/src/__tests__/Surface.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import * as PropTypes from "prop-types";
import { FoundationAdapter } from "../adapter";
import Surface from "../Surface";
describe("Surface", () => {
it("Should have mdc-dialog__surface classname", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.hasClass("mdc-dialog__surface")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/dialog/src/adapter.ts
================================================
/**
* Foundation adapters.
*/
import {
util as dialogUtil,
} from "@material/dialog/dist/mdc.dialog";
/**
* Container adapter
*/
export class ContainerAdapter {
public hasClass(_className: string): boolean {
return false;
}
public addClass(_className: string) {
}
public removeClass(_className: string) {
}
public setAttr(_attr: string, _val: string) {
}
public registerInteractionHandler(_evt: string, _handler: EventListener) {
}
public deregisterInteractionHandler(_evt: string, _handler: EventListener) {
}
public notifyAccept() {
}
public notifyCancel() {
}
}
export class SurfaceAdapter {
public registerSurfaceInteractionHandler(_evt: string, _handler: EventListener) {
}
public deregisterSurfaceInteractionHandler(_evt: string, _handler: EventListener) {
}
public numFocusableTargets(): number {
return 0;
}
public setDialogFocusFirstTarget() {
}
public setInitialFocus() {
}
public getFocusableElements(): Element[] {
return [];
}
}
/**
* Composite adapter for MDCDialogFoundation
*/
export class FoundationAdapter {
private containerAdapter: ContainerAdapter;
private surfaceAdapter: SurfaceAdapter;
constructor() {
this.containerAdapter = new ContainerAdapter();
this.surfaceAdapter = new SurfaceAdapter();
}
public setContainerAdapter(containerAdapter: ContainerAdapter) {
this.containerAdapter = containerAdapter;
}
public setSurfaceAdapter(surfaceAdapter: SurfaceAdapter) {
this.surfaceAdapter = surfaceAdapter;
}
/* Container */
public hasClass(className: string): boolean {
return this.containerAdapter.hasClass(className);
}
public addClass(className: string) {
this.containerAdapter.addClass(className);
}
public removeClass(className: string) {
this.containerAdapter.removeClass(className);
}
public setAttr(attr: string, val: string) {
this.containerAdapter.setAttr(attr, val);
}
public registerInteractionHandler(evt: string, handler: EventListener) {
this.containerAdapter.registerInteractionHandler(evt, handler);
}
public deregisterInteractionHandler(evt: string, handler: EventListener) {
this.containerAdapter.deregisterInteractionHandler(evt, handler);
}
public notifyAccept() {
this.containerAdapter.notifyAccept();
}
public notifyCancel() {
this.containerAdapter.notifyCancel();
}
/* Surface */
public registerSurfaceInteractionHandler(evt: string, handler: EventListener) {
this.surfaceAdapter.registerSurfaceInteractionHandler(evt, handler);
}
public deregisterSurfaceInteractionHandler(evt: string, handler: EventListener) {
this.surfaceAdapter.deregisterSurfaceInteractionHandler(evt, handler);
}
public numFocusableTargets(): number {
return this.surfaceAdapter.numFocusableTargets();
}
public setDialogFocusFirstTarget() {
this.surfaceAdapter.setDialogFocusFirstTarget();
}
public setInitialFocus() {
this.surfaceAdapter.setInitialFocus();
}
public getFocusableElements(): Element[] {
return this.surfaceAdapter.getFocusableElements();
}
/* Common */
public eventTargetHasClass(target: EventTarget & Element, className: string): boolean {
return target.classList.contains(className);
}
public registerDocumentKeydownHandler(handler: EventListener) {
document.addEventListener("keydown", handler);
}
public deregisterDocumentKeydownHandler(handler: EventListener) {
document.removeEventListener("keydown", handler);
}
public registerFocusTrappingHandler(handler: EventListener) {
document.addEventListener("focus", handler, true);
}
public deregisterFocusTrappingHandler(handler: EventListener) {
document.removeEventListener("focus", handler, true);
}
public getFocusedTarget(): Element {
return document.activeElement;
}
public setFocusedTarget(target: EventTarget & HTMLElement) {
target.focus();
}
public makeElementUntabbable(el: Element) {
el.setAttribute("tabindex", "-1");
}
public saveElementTabState(el: Element) {
dialogUtil.saveElementTabState(el);
}
public restoreElementTabState(el: Element) {
dialogUtil.restoreElementTabState(el);
}
public addBodyClass(className: string) {
document.body.classList.add(className);
}
public removeBodyClass(className: string) {
document.body.classList.remove(className);
}
public setBodyAttr(attr: string, val: string) {
document.body.setAttribute(attr, val);
}
public rmBodyAttr(attr: string) {
document.body.removeAttribute(attr);
}
/**
* MDCFoundation accepts only object as adapter
* So we create object-proxy of instance.
*/
public toObject(): {} {
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const object = {};
keys.forEach((key: string) => {
object[key] = (...args) => this[key](...args);
});
return object;
}
}
================================================
FILE: packages/dialog/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-dialog";
================================================
FILE: packages/dialog/src/index.ts
================================================
import Backdrop from "./Backdrop";
import Body from "./Body";
import Container from "./Container";
import Footer from "./Footer";
import Header from "./Header";
import Surface from "./Surface";
export default class Dialog extends Container {
public static Backdrop = Backdrop;
public static Body = Body;
public static Footer = Footer;
public static Header = Header;
public static Surface = Surface;
}
================================================
FILE: packages/dialog/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/dialog/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/drawer/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/drawer/package.json
================================================
{
"name": "@react-mdc/drawer",
"description": "React wrapper of @material/drawer",
"version": "0.1.13",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/drawer": "^0.4.0",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/prop-types": "^15.5.1",
"@types/react": "^15.0.18",
"@types/react-dom": "^15.5.0",
"classnames": "^2.2.5",
"immutable": "^3.8.1",
"prop-types": "^15.5.8",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/drawer/src/Permanent/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { BASE_CLASS_NAME } from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "nav";
}
}
================================================
FILE: packages/drawer/src/Permanent/Content.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { BASE_CLASS_NAME } from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__content`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Content extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/drawer/src/Permanent/ToolbarSpacer.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { BASE_CLASS_NAME } from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__toolbar-spacer`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class ToolbarSpacer extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/drawer/src/Permanent/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-permanent-drawer classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-permanent-drawer")).toBeTruthy();
});
it("Should have nav element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("nav").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Permanent/__tests__/Content.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Content from "../Content";
describe("Content", () => {
it("Should have mdc-permanent-drawer__content classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-permanent-drawer__content")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Permanent/__tests__/ToolbarSpacer.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import ToolbarSpacer from "../ToolbarSpacer";
describe("ToolbarSpacer", () => {
it("Should have mdc-permanent-drawer__toolbar-spacer classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-permanent-drawer__toolbar-spacer")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Permanent/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-permanent-drawer";
export const SELECTED_CLASS_NAME = `${BASE_CLASS_NAME}--selected`;
================================================
FILE: packages/drawer/src/Permanent/index.ts
================================================
import Container from "./Container";
import Content from "./Content";
import ToolbarSpacer from "./ToolbarSpacer";
export default class Permanent extends Container {
public static Content = Content;
public static ToolbarSpacer = ToolbarSpacer;
}
================================================
FILE: packages/drawer/src/Temporary/Container.tsx
================================================
import * as React from "react";
import * as classNames from "classnames";
import * as PropTypes from "prop-types";
import { MDCTemporaryDrawerFoundation } from "@material/drawer/dist/mdc.drawer";
import {
Map,
OrderedSet,
Set,
} from "immutable";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { includes } from "@react-mdc/base/lib/util";
import { ContainerAdapter, FoundationAdapter } from "./adapter";
import { BASE_CLASS_NAME } from "./constants";
import * as drawerUtil from "./drawerUtil";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
open?: boolean,
rtl?: boolean,
style?: { [name: string]: any },
onOpenDrawer?: (meta: Meta) => void,
onCloseDrawer?: (meta: Meta) => void,
className?: string,
};
export type ChildProps = {
className?: string,
};
export type State = {
foundationClasses: Set,
foundationCssVars: Map,
foundationEventListeners: Map>,
open: boolean,
};
export type ChildContext = {
adapter: FoundationAdapter,
};
const {
cssClasses: {
OPEN: OPEN_CLASS_NAME,
},
strings: {
OPACITY_VAR_NAME,
},
} = MDCTemporaryDrawerFoundation;
export class Meta extends ClassNameMetaBase {
public static displayName = "Container";
public static childContextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter),
};
public static defaultProps = {
rtl: false,
style: {},
};
public state: State = {
foundationClasses: OrderedSet(),
foundationCssVars: Map(),
foundationEventListeners: Map>(),
open: false,
};
private adapter: FoundationAdapter;
private foundation: MDCTemporaryDrawerFoundation;
constructor(props) {
super(props);
this.adapter = new FoundationAdapter();
this.foundation = new MDCTemporaryDrawerFoundation(this.adapter.toObject());
}
public getChildContext(): ChildContext {
return {
adapter: this.adapter,
};
}
// Sync props and internal state
public componentWillReceiveProps(props: MetaProps) {
if (props.open !== this.state.open) {
if (props.open) {
this.foundation.open();
} else {
this.foundation.close();
}
}
}
// Foundation lifecycle
public componentDidMount() {
this.adapter.setContainerAdapter(new ContainerAdapterImpl(this));
this.foundation.init();
}
public componentWillUnmount() {
this.foundation.destroy();
this.adapter.setContainerAdapter(new ContainerAdapter());
}
public getClassName(_props: MetaProps, state: State): string {
return classNames(
CLASS_NAME,
state.foundationClasses.toJS(),
);
}
protected renderNativeDOMProps() {
return {
cssVariables: this.state.foundationCssVars.toJS(),
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [this.getClassName(this.props, this.state)];
}
}
class ContainerAdapterImpl extends ContainerAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public addClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.add(className),
}));
// MDCTemporaryDrawerFoundation does not provide opening/closing event.
// But we can assume open/close by adding/removing OPEN_CLASS_NAME
if (className === OPEN_CLASS_NAME) {
this.element.setState({
open: true,
});
if (this.element.props.onOpenDrawer) {
this.element.props.onOpenDrawer(this.element);
}
}
}
public removeClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.remove(className),
}));
// MDCTemporaryDrawerFoundation does not provide opening/closing event.
// But we can assume open/close by adding/removing OPEN_CLASS_NAME
if (className === OPEN_CLASS_NAME) {
this.element.setState({
open: false,
});
if (this.element.props.onCloseDrawer) {
this.element.props.onCloseDrawer(this.element);
}
}
}
public hasClass(className: string): boolean {
return includes(
this.element.getClassName(this.element.props, this.element.state).split(/\s+/),
className);
}
public registerInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
drawerUtil.remapEvent(evt, window),
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
drawerUtil.remapEvent(evt, window),
OrderedSet(),
(x) => x.delete(handler),
),
}));
}
public registerTransitionEndHandler(handler: EventListener) {
const evt = "transitionend";
this.registerInteractionHandler(evt, handler);
}
public deregisterTransitionEndHandler(handler: EventListener) {
const evt = "transitionend";
this.deregisterInteractionHandler(evt, handler);
}
public registerDocumentKeydownHandler(handler: EventListener) {
document.addEventListener("keydown", handler);
}
public deregisterDocumentKeydownHandler(handler: EventListener) {
document.removeEventListener("keydown", handler);
}
public updateCssVariable(value: string) {
this.element.setState((state) => ({
foundationCssVars: state.foundationCssVars.set(OPACITY_VAR_NAME, value),
}));
}
public saveElementTabState(el: Element) {
drawerUtil.saveElementTabState(el);
}
public restoreElementTabState(el: Element) {
drawerUtil.restoreElementTabState(el);
}
public makeElementUntabbable(el: Element) {
el.setAttribute("tabindex", "-1");
}
public isRtl(): boolean {
return this.element.props.rtl || false;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"open",
"rtl",
"style",
"onOpenDrawer",
"onCloseDrawer",
];
}
protected getChildComponent() {
return "aside";
}
}
================================================
FILE: packages/drawer/src/Temporary/Content.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__content`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Content extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/drawer/src/Temporary/Drawer.tsx
================================================
import * as React from "react";
import * as ReactDOM from "react-dom";
import { MDCTemporaryDrawerFoundation } from "@material/drawer/dist/mdc.drawer";
import {
Map,
OrderedSet,
Set,
} from "immutable";
import * as PropTypes from "prop-types";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { DrawerAdapter, FoundationAdapter } from "./adapter";
import { BASE_CLASS_NAME } from "./constants";
import * as drawerUtil from "./drawerUtil";
export const CLASS_NAME = `${BASE_CLASS_NAME}__drawer`;
const {
strings: {
FOCUSABLE_ELEMENTS,
},
} = MDCTemporaryDrawerFoundation;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export type State = {
foundationCssVars: Map,
foundationEventListeners: Map>,
};
export type Context = {
adapter: FoundationAdapter,
};
export class Meta extends ClassNameMetaBase {
public static contextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter).isRequired,
};
public context: Context;
public state: State = {
foundationCssVars: Map(),
foundationEventListeners: Map>(),
};
public componentDidMount() {
this.context.adapter.setDrawerAdapter(new DrawerAdapterImpl(this));
}
public componentWillUnmount() {
this.context.adapter.setDrawerAdapter(new DrawerAdapter());
}
protected renderNativeDOMProps() {
return {
cssVariables: this.state.foundationCssVars.toJS(),
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderBaseClassName() {
return CLASS_NAME;
}
}
class DrawerAdapterImpl extends DrawerAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public registerDrawerInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
drawerUtil.remapEvent(evt, window),
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterDrawerInteractionHandler(evt: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
drawerUtil.remapEvent(evt, window),
OrderedSet(),
(x) => x.delete(handler),
),
}));
}
public hasNecessaryDom(): boolean {
return this.getDOMNode() != null;
}
public getDrawerWidth(): number {
return this.getDOMNode().getBoundingClientRect().width;
}
public setTranslateX(value: number) {
if (value == null) {
this.element.setState((state) => ({
foundationCssVars: state.foundationCssVars.delete(drawerUtil.getTransformPropertyName()),
}));
} else {
this.element.setState((state) => ({
foundationCssVars: state
.foundationCssVars
.set(drawerUtil.getTransformPropertyName(), `translateX(${value}px)`),
}));
}
}
public isDrawer(el: Element): boolean {
return this.getDOMNode() === el;
}
public getFocusableElements(): NodeListOf {
return this.getDOMNode().querySelectorAll(FOCUSABLE_ELEMENTS);
}
public getDOMNode(): Element {
return ReactDOM.findDOMNode(this.element);
}
}
export default class Drawer extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "nav";
}
}
================================================
FILE: packages/drawer/src/Temporary/Header.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__header`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Header extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "header";
}
}
================================================
FILE: packages/drawer/src/Temporary/HeaderContent.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__header-content`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class HeaderContent extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/drawer/src/Temporary/ToolbarSpacer.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__toolbar-spacer`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class ToolbarSpacer extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/drawer/src/Temporary/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
import Drawer from "../Drawer";
describe("Container", () => {
it("Should have mdc-temporary-drawer classname", () => {
const wrapper = enzyme.mount(
{/* Drawer is required component of Container */}
,
);
expect(wrapper.hasClass("mdc-temporary-drawer")).toBeTruthy();
});
it("Should have aside element as default component", () => {
const wrapper = enzyme.mount(
{/* Drawer is required component of Container */}
,
);
expect(wrapper.find("aside").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/drawer/src/Temporary/__tests__/Content.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Content from "../Content";
describe("Content", () => {
it("Should have mdc-temporary-drawer__content classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-temporary-drawer__content")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Temporary/__tests__/Drawer.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import * as PropTypes from "prop-types";
import { FoundationAdapter } from "../adapter";
import Drawer from "../Drawer";
describe("Container", () => {
it("Should have mdc-temporary-drawer__drawer classname", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.hasClass("mdc-temporary-drawer__drawer")).toBeTruthy();
});
it("Should have nav element as default component", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.find("nav").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/drawer/src/Temporary/__tests__/Header.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Header from "../Header";
describe("Header", () => {
it("Should have mdc-temporary-drawer__header classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-temporary-drawer__header")).toBeTruthy();
});
it("Should have header element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("header").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Temporary/__tests__/HeaderContent.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import HeaderContent from "../HeaderContent";
describe("HeaderContent", () => {
it("Should have mdc-temporary-drawer__header-content classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-temporary-drawer__header-content")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Temporary/__tests__/ToolbarSpacer.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import ToolbarSpacer from "../ToolbarSpacer";
describe("ToolbarSpacer", () => {
it("Should have mdc-temporary-drawer__toolbar-spacer classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-temporary-drawer__toolbar-spacer")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/drawer/src/Temporary/adapter.ts
================================================
/**
* Foundation adapters.
*/
function createEmptyNodeList(): NodeListOf {
const item: (index: number) => Element = () => { throw new Error("This is an empty node list"); };
return {
...([]),
item,
} as NodeListOf;
}
/**
* Container adapter interface
* Default implementations are noop and returns meaningless value.
*/
export class ContainerAdapter {
public addClass(_className: string) {
}
public removeClass(_className: string) {
}
public hasClass(_className: string): boolean {
return false;
}
public registerInteractionHandler(_evt: string, _handler: EventListener) {
}
public deregisterInteractionHandler(_evt: string, _handler: EventListener) {
}
public registerTransitionEndHandler(_handler: EventListener) {
}
public deregisterTransitionEndHandler(_handler: EventListener) {
}
public registerDocumentKeydownHandler(_handler: EventListener) {
}
public deregisterDocumentKeydownHandler(_handler: EventListener) {
}
public updateCssVariable(_value: string) {
}
public saveElementTabState(_el: Element) {
}
public restoreElementTabState(_el: Element) {
}
public makeElementUntabbable(_el: Element) {
}
public isRtl(): boolean {
return false;
}
}
/**
* Drawer adapter interface
* Default implementations are noop and returns meaningless value.
*/
export class DrawerAdapter {
public registerDrawerInteractionHandler(_evt: string, _handler: EventListener) {
}
public deregisterDrawerInteractionHandler(_evt: string, _handler: EventListener) {
}
public getDrawerWidth(): number {
return 0;
}
public setTranslateX(_value: number) {
}
public isDrawer(_el: Element): boolean {
return false;
}
public getFocusableElements(): NodeListOf {
return createEmptyNodeList();
}
public hasNecessaryDom(): boolean {
return false;
}
}
/**
* Composite adapter for MDCTemporaryDrawerFoundation
*/
export class FoundationAdapter {
private containerAdapter: ContainerAdapter;
private drawerAdapter: DrawerAdapter;
constructor() {
this.containerAdapter = new ContainerAdapter();
this.drawerAdapter = new DrawerAdapter();
}
public setContainerAdapter(containerAdapter: ContainerAdapter) {
this.containerAdapter = containerAdapter;
}
public setDrawerAdapter(drawerAdapter: DrawerAdapter) {
this.drawerAdapter = drawerAdapter;
}
public addClass(className: string) {
this.containerAdapter.addClass(className);
}
public removeClass(className: string) {
this.containerAdapter.removeClass(className);
}
public hasClass(className: string): boolean {
return this.containerAdapter.hasClass(className);
}
public registerInteractionHandler(evt: string, handler: EventListener) {
this.containerAdapter.registerInteractionHandler(evt, handler);
}
public deregisterInteractionHandler(evt: string, handler: EventListener) {
this.containerAdapter.deregisterInteractionHandler(evt, handler);
}
public registerTransitionEndHandler(handler: EventListener) {
this.containerAdapter.registerTransitionEndHandler(handler);
}
public deregisterTransitionEndHandler(handler: EventListener) {
this.containerAdapter.deregisterTransitionEndHandler(handler);
}
public registerDocumentKeydownHandler(handler: EventListener) {
this.containerAdapter.registerDocumentKeydownHandler(handler);
}
public deregisterDocumentKeydownHandler(handler: EventListener) {
this.containerAdapter.deregisterDocumentKeydownHandler(handler);
}
public updateCssVariable(value: string) {
this.containerAdapter.updateCssVariable(value);
}
public saveElementTabState(el: Element) {
this.containerAdapter.saveElementTabState(el);
}
public restoreElementTabState(el: Element) {
this.containerAdapter.restoreElementTabState(el);
}
public makeElementUntabbable(el: Element) {
this.containerAdapter.makeElementUntabbable(el);
}
public isRtl(): boolean {
return this.containerAdapter.isRtl();
}
public registerDrawerInteractionHandler(evt: string, handler: EventListener) {
this.drawerAdapter.registerDrawerInteractionHandler(evt, handler);
}
public deregisterDrawerInteractionHandler(evt: string, handler: EventListener) {
this.drawerAdapter.deregisterDrawerInteractionHandler(evt, handler);
}
public getDrawerWidth(): number {
return this.drawerAdapter.getDrawerWidth();
}
public setTranslateX(value: number) {
this.drawerAdapter.setTranslateX(value);
}
public isDrawer(el: Element): boolean {
return this.drawerAdapter.isDrawer(el);
}
public getFocusableElements(): NodeListOf {
return this.drawerAdapter.getFocusableElements();
}
public hasNecessaryDom(): boolean {
return this.drawerAdapter.hasNecessaryDom();
}
/**
* MDCFoundation accepts only object as adapter
* So we create object-proxy of instance.
*/
public toObject(): {} {
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const object = {};
keys.forEach((key: string) => {
object[key] = (...args) => this[key](...args);
});
return object;
}
}
================================================
FILE: packages/drawer/src/Temporary/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-temporary-drawer";
export const SELECTED_CLASS_NAME = `${BASE_CLASS_NAME}--selected`;
================================================
FILE: packages/drawer/src/Temporary/drawerUtil.ts
================================================
/* eslint-disable */
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const TAB_DATA = "data-mdc-tabindex";
const TAB_DATA_HANDLED = "data-mdc-tabindex-handled";
let _storedTransformPropertyName;
let _supportsPassive;
// Remap touch events to pointer events, if the browser doesn't support touch events.
export function remapEvent(eventName, globalObj = window) {
if (!("ontouchstart" in globalObj.document)) {
switch (eventName) {
case "touchstart":
return "pointerdown";
case "touchmove":
return "pointermove";
case "touchend":
return "pointerup";
default:
return eventName;
}
}
return eventName;
}
// Choose the correct transform property to use on the current browser.
export function getTransformPropertyName(globalObj = window, forceRefresh = false) {
if (_storedTransformPropertyName === undefined || forceRefresh) {
const el = globalObj.document.createElement("div");
const transformPropertyName = ("transform" in el.style ? "transform" : "-webkit-transform");
_storedTransformPropertyName = transformPropertyName;
}
return _storedTransformPropertyName;
}
// Determine whether the current browser supports CSS properties.
export function supportsCssCustomProperties(globalObj: any = window) {
if ("CSS" in globalObj) {
return globalObj.CSS.supports("(--color: red)");
}
return false;
}
// Determine whether the current browser supports passive event listeners, and if so, use them.
export function applyPassive(globalObj: any = window, forceRefresh = false) {
if (_supportsPassive === undefined || forceRefresh) {
let isSupported = false;
try {
const checker = {};
Object.defineProperty(checker, "passive", { get: () => { isSupported = true; } });
globalObj.document.addEventListener("test", null, checker);
} catch (e) { }
_supportsPassive = isSupported;
}
return _supportsPassive ? {passive: true} : false;
}
// Save the tab state for an element.
export function saveElementTabState(el) {
if (el.hasAttribute("tabindex")) {
el.setAttribute(TAB_DATA, el.getAttribute("tabindex"));
}
el.setAttribute(TAB_DATA_HANDLED, true);
}
// Restore the tab state for an element, if it was saved.
export function restoreElementTabState(el) {
// Only modify elements we've already handled, in case anything was dynamically added since we saved state.
if (el.hasAttribute(TAB_DATA_HANDLED)) {
if (el.hasAttribute(TAB_DATA)) {
el.setAttribute("tabindex", el.getAttribute(TAB_DATA));
el.removeAttribute(TAB_DATA);
} else {
el.removeAttribute("tabindex");
}
el.removeAttribute(TAB_DATA_HANDLED);
}
}
================================================
FILE: packages/drawer/src/Temporary/index.ts
================================================
import Container from "./Container";
import Content from "./Content";
import Drawer from "./Drawer";
import Header from "./Header";
import HeaderContent from "./HeaderContent";
import ToolbarSpacer from "./ToolbarSpacer";
export default class Temporary extends Container {
public static Content = Content;
public static Drawer = Drawer;
public static Header = Header;
public static HeaderContent = HeaderContent;
public static ToolbarSpacer = ToolbarSpacer;
}
================================================
FILE: packages/drawer/src/index.ts
================================================
import Permanent from "./Permanent";
import Temporary from "./Temporary";
export {
Permanent,
Temporary,
};
export default {
Permanent,
Temporary,
};
================================================
FILE: packages/drawer/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/drawer/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/elevation/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/elevation/package.json
================================================
{
"name": "@react-mdc/elevation",
"description": "React wrapper of @material/elevation",
"version": "0.1.9",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/elevation": "^0.1.2",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/elevation/src/Elevation.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { BASE_CLASS_NAME } from "./constants";
import { ZSpace } from "./types";
import { classNameForZSpace } from "./utils";
export const propertyClassNames = {
TRANSITION: `${BASE_CLASS_NAME}-transition`,
};
export type MetaProps = {
zSpace: ZSpace,
transition?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Elevation component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName(_c) {
return classNameForZSpace(this.props.zSpace);
}
protected renderClassValues(_c: ChildProps) {
return [{
[propertyClassNames.TRANSITION]: this.props.transition,
}];
}
}
export default class Elevation extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"zSpace",
"transition",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/elevation/src/__tests__/Elevation.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Elevation from "../Elevation";
describe("Elevation", () => {
it("Should have mdc-elevation--z{zSpace} classname", () => {
for (let i = 0; i <= 24; i++) {
const wrapper = enzyme.mount();
expect(wrapper.hasClass(`mdc-elevation--z${i}`)).toBeTruthy();
}
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-elevation--z10")).toBeTruthy();
expect(wrapper.hasClass("mdc-elevation-transition")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-elevation--z10")).toBeTruthy();
expect(wrapper.hasClass("mdc-elevation-transition")).toBeFalsy();
});
});
================================================
FILE: packages/elevation/src/__tests__/utils.spec.ts
================================================
import "jest";
import { ZSpace } from "../types";
import * as utils from "../utils";
describe("classNameForZSpace", () => {
it("Should return mdc-elevation--z{zSpace} as result", () => {
for (let i = 0; i <= 24; i++) {
expect(utils.classNameForZSpace(i as ZSpace)).toBe(`mdc-elevation--z${i}`);
}
});
});
================================================
FILE: packages/elevation/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-elevation";
================================================
FILE: packages/elevation/src/index.tsx
================================================
export { default } from "./Elevation";
================================================
FILE: packages/elevation/src/types.ts
================================================
export type ZSpace =
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24;
================================================
FILE: packages/elevation/src/utils.ts
================================================
import {BASE_CLASS_NAME} from "./constants";
import {ZSpace} from "./types";
export function classNameForZSpace(zSpace: ZSpace): string {
if (zSpace < 0 || zSpace > 24) {
throw new TypeError("z-space should be a number between 0-24");
}
return `${BASE_CLASS_NAME}--z${zSpace}`;
}
================================================
FILE: packages/elevation/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/elevation/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/fab/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/fab/package.json
================================================
{
"name": "@react-mdc/fab",
"description": "React wrapper of @material/fab",
"version": "0.1.9",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/fab": "^0.3.0",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/fab/src/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export const propertyClassNames = {
MINI: `${CLASS_NAME}--mini`,
PLAIN: `${CLASS_NAME}--plain`,
};
export type MetaProps = {
mini?: boolean,
plain?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Fab container
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.MINI]: this.props.mini,
[propertyClassNames.PLAIN]: this.props.plain,
}];
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"mini",
"plain",
];
}
protected getChildComponent() {
return "button";
}
}
================================================
FILE: packages/fab/src/Icon.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__icon`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Fab icon component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Icon extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "span";
}
}
================================================
FILE: packages/fab/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-fab classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-fab")).toBeTruthy();
});
it("Should have button element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("button").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-fab")).toBeTruthy();
expect(wrapper.hasClass("mdc-fab--mini")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-fab")).toBeTruthy();
expect(wrapper.hasClass("mdc-fab--plain")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-fab")).toBeTruthy();
expect(wrapper.hasClass("mdc-fab--mini")).toBeFalsy();
expect(wrapper.hasClass("mdc-fab--plain")).toBeFalsy();
});
});
================================================
FILE: packages/fab/src/__tests__/Icon.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Icon from "../Icon";
describe("Icon", () => {
it("Should have mdc-fab__icon classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-fab__icon")).toBeTruthy();
});
it("Should have span element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("span").exists()).toBeTruthy();
});
});
================================================
FILE: packages/fab/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-fab";
================================================
FILE: packages/fab/src/index.ts
================================================
import Container from "./Container";
import Icon from "./Icon";
export default class FAB extends Container {
public static Icon = Icon;
}
================================================
FILE: packages/fab/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/fab/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/form-field/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/form-field/package.json
================================================
{
"name": "@react-mdc/form-field",
"description": "React wrapper of @material/form-field",
"version": "0.1.8",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/form-field": "^0.1.1",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/form-field/src/FormField.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { BASE_CLASS_NAME } from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export const propertyClassNames = {
ALIGN_END: `${CLASS_NAME}--align-end`,
};
export type MetaProps = {
alignEnd?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Form field component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.ALIGN_END]: this.props.alignEnd,
}];
}
}
export default class FormField extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"alignEnd",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/form-field/src/__tests__/FormField.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import FormField from "../FormField";
describe("FormField", () => {
it("Should have mdc-form-field classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-form-field")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-form-field")).toBeTruthy();
expect(wrapper.hasClass("mdc-form-field--align-end")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-form-field")).toBeTruthy();
expect(wrapper.hasClass("mdc-form-field--align-end")).toBeFalsy();
});
});
================================================
FILE: packages/form-field/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-form-field";
================================================
FILE: packages/form-field/src/index.tsx
================================================
export { default } from "./FormField";
================================================
FILE: packages/form-field/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/form-field/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/layout-grid/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/layout-grid/package.json
================================================
{
"name": "@react-mdc/layout-grid",
"description": "React wrapper of @material/layout-grid",
"version": "0.1.9",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/layout-grid": "^0.1.0",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/layout-grid/src/Cell.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { CELL_BASE_CLASS_NAME } from "./constants";
import { Alignment, GridNumber } from "./types";
import * as utils from "./utils";
export const CLASS_NAME = CELL_BASE_CLASS_NAME;
export type MetaProps = {
span?: GridNumber,
spanDesktop?: GridNumber,
spanTablet?: GridNumber,
spanPhone?: GridNumber,
order?: GridNumber,
align?: Alignment,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Grid cell component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
const {
span,
spanDesktop,
spanTablet,
spanPhone,
order,
align,
} = this.props;
const classes: string[] = [];
if (span != null) {
classes.push(utils.classNameForCellSpan(span));
}
if (spanDesktop != null) {
classes.push(utils.classNameForCellSpan(spanDesktop, "desktop"));
}
if (spanTablet != null) {
classes.push(utils.classNameForCellSpan(spanTablet, "tablet"));
}
if (spanPhone != null) {
classes.push(utils.classNameForCellSpan(spanPhone, "phone"));
}
if (align != null) {
classes.push(utils.classNameForCellAlignment(align));
}
if (order != null) {
classes.push(utils.classNameForCellOrder(order));
}
return classes;
}
}
export default class Cell extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"span",
"spanDesktop",
"spanTablet",
"spanPhone",
"order",
"align",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/layout-grid/src/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
GUTTER_CSS_VARIABLE,
MARGIN_CSS_VARIABLE,
} from "./constants";
import { Gutter, Margin } from "./types";
import * as utils from "./utils";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
margin?: Margin,
gutter?: Gutter,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Wrapper component of mdc-layout-grid
*/
export class Meta extends ClassNameMetaBase {
protected renderNativeDOMProps() {
const cssVariables = {};
if (this.props.margin != null) {
cssVariables[MARGIN_CSS_VARIABLE] = utils.normalizeMarginAndGutter(this.props.margin);
}
if (this.props.gutter != null) {
cssVariables[GUTTER_CSS_VARIABLE] = utils.normalizeMarginAndGutter(this.props.gutter);
}
return { cssVariables };
}
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"margin",
"gutter",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/layout-grid/src/__tests__/Cell.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Cell from "../Cell";
describe("Cell", () => {
it("Should have mdc-layout-grid__cell classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--span-1")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--span-2-desktop")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--span-3-tablet")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--span-4-phone")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--order-5")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--align-top")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--align-middle")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid__cell")).toBeTruthy();
expect(wrapper.hasClass("mdc-layout-grid__cell--align-bottom")).toBeTruthy();
});
});
================================================
FILE: packages/layout-grid/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import {
NativeDOMAdapter,
} from "@react-mdc/base";
import { GUTTER_CSS_VARIABLE, MARGIN_CSS_VARIABLE } from "../constants";
import Container from "../Container";
import { MetaProps } from "../Container";
describe("Container", () => {
it("Should have mdc-layout-grid classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-layout-grid")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should render property CSS properties", () => {
let wrapper: enzyme.ShallowWrapper & MetaProps, any>;
wrapper = enzyme.shallow().shallow();
expect(wrapper.find(NativeDOMAdapter).prop("cssVariables")).toEqual({
[MARGIN_CSS_VARIABLE]: "8px",
});
wrapper = enzyme.shallow().shallow();
expect(wrapper.find(NativeDOMAdapter).prop("cssVariables")).toEqual({
[GUTTER_CSS_VARIABLE]: "8px",
});
wrapper = enzyme.shallow().shallow();
expect(wrapper.find(NativeDOMAdapter).prop("cssVariables")).toEqual({
[MARGIN_CSS_VARIABLE]: "8px",
[GUTTER_CSS_VARIABLE]: "16px",
});
wrapper = enzyme.shallow().shallow();
expect(wrapper.find(NativeDOMAdapter).prop("cssVariables")).toEqual({
[MARGIN_CSS_VARIABLE]: "foo",
});
wrapper = enzyme.shallow().shallow();
expect(wrapper.find(NativeDOMAdapter).prop("cssVariables")).toEqual({
[GUTTER_CSS_VARIABLE]: "3em",
});
});
});
================================================
FILE: packages/layout-grid/src/__tests__/utils.spec.ts
================================================
import "jest";
import { GUTTER_CSS_VARIABLE, MARGIN_CSS_VARIABLE } from "../constants";
import { GridNumber, Screen } from "../types";
import * as utils from "../utils";
describe("classNameForCellSpan()", () => {
it("Should return span classname", () => {
for (let i = 1; i <= 12; i++) {
expect(utils.classNameForCellSpan(i as GridNumber)).toBe(
`mdc-layout-grid__cell--span-${i}`,
);
}
});
it("Should return span classname with screens", () => {
(["desktop", "phone", "tablet"] as [Screen]).forEach((screen) => {
for (let i = 1; i <= 12; i++) {
expect(utils.classNameForCellSpan(i as GridNumber, screen)).toBe(
`mdc-layout-grid__cell--span-${i}-${screen}`,
);
}
});
});
});
describe("classNameForCellOrder()", () => {
it("Should return order classname", () => {
for (let i = 1; i <= 12; i++) {
expect(utils.classNameForCellOrder(i as GridNumber)).toBe(
`mdc-layout-grid__cell--order-${i}`,
);
}
});
});
describe("classNameForCellAlignment()", () => {
it("Should return align classname", () => {
expect(utils.classNameForCellAlignment("top")).toBe(
"mdc-layout-grid__cell--align-top",
);
expect(utils.classNameForCellAlignment("middle")).toBe(
"mdc-layout-grid__cell--align-middle",
);
expect(utils.classNameForCellAlignment("bottom")).toBe(
"mdc-layout-grid__cell--align-bottom",
);
});
});
================================================
FILE: packages/layout-grid/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-layout-grid";
export const CELL_BASE_CLASS_NAME = `${BASE_CLASS_NAME}__cell`;
export const MARGIN_CSS_VARIABLE = "--mdc-layout-grid-margin";
export const GUTTER_CSS_VARIABLE = "--mdc-layout-grid-gutter";
================================================
FILE: packages/layout-grid/src/index.ts
================================================
import Cell from "./Cell";
import Container from "./Container";
export default class LayoutGrid extends Container {
public static Cell = Cell;
}
================================================
FILE: packages/layout-grid/src/types.ts
================================================
export type GridNumber =
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12;
export type Screen =
"desktop" |
"phone" |
"tablet";
export type Alignment =
"top" |
"middle" |
"bottom";
export type Margin = number | string;
export type Gutter = number | string;
================================================
FILE: packages/layout-grid/src/utils.ts
================================================
import { CELL_BASE_CLASS_NAME } from "./constants";
import { Alignment, GridNumber, Gutter, Margin, Screen } from "./types";
export function classNameForCellSpan(span: GridNumber, screen: Screen | null = null): string {
const className = `${CELL_BASE_CLASS_NAME}--span-${span}`;
if (screen != null) {
return `${className}-${screen}`;
} else {
return className;
}
}
export function classNameForCellOrder(order: GridNumber): string {
return `${CELL_BASE_CLASS_NAME}--order-${order}`;
}
export function classNameForCellAlignment(alignment: Alignment): string {
return `${CELL_BASE_CLASS_NAME}--align-${alignment}`;
}
export function normalizeMarginAndGutter(value: Margin | Gutter): string {
if (typeof value === "number") {
return `${value}px`;
} else {
return value;
}
}
================================================
FILE: packages/layout-grid/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/layout-grid/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/list/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/list/package.json
================================================
{
"name": "@react-mdc/list",
"description": "React wrapper of @material/list",
"version": "0.1.9",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/list": "^0.2.0",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/list/src/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export const propertyClassNames = {
DENSE: `${CLASS_NAME}--dense`,
TWO_LINE: `${CLASS_NAME}--two-line`,
AVATAR_LIST: `${CLASS_NAME}--avatar-list`,
};
export type MetaProps = {
dense?: boolean,
twoLine?: boolean,
avatarList?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* List container component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.DENSE]: this.props.dense,
[propertyClassNames.TWO_LINE]: this.props.twoLine,
[propertyClassNames.AVATAR_LIST]: this.props.avatarList,
}];
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"dense",
"twoLine",
"avatarList",
];
}
protected getChildComponent() {
return "ul";
}
}
================================================
FILE: packages/list/src/Divider.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}-divider`;
export const propertyClassNames = {
INSET: `${CLASS_NAME}--inset`,
};
export type MetaProps = {
inset?: boolean,
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* List divider component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return [{
[propertyClassNames.INSET]: this.props.inset,
}];
}
}
// li with role="separator" as default
function SeparatorLi(props: React.HTMLProps) {
return ;
}
export default class Divider extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"inset",
];
}
protected getChildComponent(): React.SFC> {
return SeparatorLi;
}
}
================================================
FILE: packages/list/src/Group/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/list/src/Group/Subheader.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__subheader`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Subheader extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "h3";
}
}
================================================
FILE: packages/list/src/Group/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-list-group classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-group")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/list/src/Group/__tests__/Subheader.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Subheader from "../Subheader";
describe("Subheader", () => {
it("Should have mdc-list-group__subheader classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-group__subheader")).toBeTruthy();
});
it("Should have h3 element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("h3").exists()).toBeTruthy();
});
});
================================================
FILE: packages/list/src/Group/constants.ts
================================================
import { BASE_CLASS_NAME as LIST_BASE_CLASS_NAME } from "../constants";
export const BASE_CLASS_NAME = `${LIST_BASE_CLASS_NAME}-group`;
================================================
FILE: packages/list/src/Group/index.ts
================================================
import Container from "./Container";
import Subheader from "./Subheader";
export default class Group extends Container {
public static Subheader = Subheader;
}
================================================
FILE: packages/list/src/Item/Container.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "li";
}
}
================================================
FILE: packages/list/src/Item/EndDetail.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__end-detail`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class EndDetail extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "span";
}
}
================================================
FILE: packages/list/src/Item/StartDetail.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__start-detail`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class StartDetail extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "span";
}
}
================================================
FILE: packages/list/src/Item/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-list-item classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-item")).toBeTruthy();
});
it("Should have li element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("li").exists()).toBeTruthy();
});
});
================================================
FILE: packages/list/src/Item/__tests__/EndDetail.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import EndDetail from "../EndDetail";
describe("EndDetail", () => {
it("Should have mdc-list-item__end-detail classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-item__end-detail")).toBeTruthy();
});
it("Should have span element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("span").exists()).toBeTruthy();
});
});
================================================
FILE: packages/list/src/Item/__tests__/StartDetail.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import StartDetail from "../StartDetail";
describe("StartDetail", () => {
it("Should have mdc-list-item__start-detail classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-item__start-detail")).toBeTruthy();
});
it("Should have span element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("span").exists()).toBeTruthy();
});
});
================================================
FILE: packages/list/src/Item/constants.ts
================================================
import { BASE_CLASS_NAME as LIST_BASE_CLASS_NAME } from "../constants";
export const BASE_CLASS_NAME = `${LIST_BASE_CLASS_NAME}-item`;
================================================
FILE: packages/list/src/Item/index.ts
================================================
import Container from "./Container";
import EndDetail from "./EndDetail";
import StartDetail from "./StartDetail";
export default class Item extends Container {
public static EndDetail = EndDetail;
public static StartDetail = StartDetail;
}
================================================
FILE: packages/list/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-list classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list")).toBeTruthy();
});
it("Should have ul element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("ul").exists()).toBeTruthy();
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--dense")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--two-line")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--avatar-list")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--dense")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--two-line")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--avatar-list")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list")).toBeTruthy();
expect(wrapper.hasClass("mdc-list--dense")).toBeFalsy();
expect(wrapper.hasClass("mdc-list--two-line")).toBeFalsy();
expect(wrapper.hasClass("mdc-list--avatar-list")).toBeFalsy();
});
});
================================================
FILE: packages/list/src/__tests__/Divider.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Divider from "../Divider";
describe("Divider", () => {
it("Should have mdc-list-divider classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-divider")).toBeTruthy();
});
it("Should have li element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("li").exists()).toBeTruthy();
expect(wrapper.find("li").prop("role")).toBe("separator");
});
it("Should render property classnames", () => {
let wrapper;
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-divider")).toBeTruthy();
expect(wrapper.hasClass("mdc-list-divider--inset")).toBeTruthy();
wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-list-divider")).toBeTruthy();
expect(wrapper.hasClass("mdc-list-divider--inset")).toBeFalsy();
});
});
================================================
FILE: packages/list/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-list";
================================================
FILE: packages/list/src/index.ts
================================================
import Container from "./Container";
import Divider from "./Divider";
import Group from "./Group";
import Item from "./Item";
export default class List extends Container {
public static Divider = Divider;
public static Group = Group;
public static Item = Item;
}
================================================
FILE: packages/list/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/list/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/radio/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/radio/package.json
================================================
{
"name": "@react-mdc/radio",
"description": "React wrapper of @material/radio",
"version": "0.1.12",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/radio": "^0.2.2",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/prop-types": "^15.5.1",
"@types/react": "^15.0.18",
"@types/react-dom": "^0.14.23",
"classnames": "^2.2.5",
"immutable": "^3.8.1",
"prop-types": "^15.5.8",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/radio/src/Background.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__background`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
/**
* Radio background component
*/
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class Background extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/radio/src/Container.tsx
================================================
import * as React from "react";
import { OrderedSet, Set } from "immutable";
import * as PropTypes from "prop-types";
import { MDCRadioFoundation } from "@material/radio/dist/mdc.radio";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { ContainerAdapter, FoundationAdapter } from "./adapter";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = BASE_CLASS_NAME;
export type ChildProps = {
className?: string,
};
export type MetaProps = {
checked?: boolean,
disabled?: boolean,
className?: string,
};
export type State = {
foundationClasses: Set,
};
export type ChildContext = {
adapter: FoundationAdapter,
};
/**
* Radio input container component
*/
export class Meta extends ClassNameMetaBase {
public static childContextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter),
};
public state: State = {
foundationClasses: OrderedSet(),
};
private adapter: FoundationAdapter;
private foundation: MDCRadioFoundation;
constructor(props) {
super(props);
this.adapter = new FoundationAdapter();
this.foundation = new MDCRadioFoundation(this.adapter.toObject());
}
public getChildContext(): ChildContext {
return {
adapter: this.adapter,
};
}
// Foundation lifecycle
public componentDidMount() {
this.adapter.setContainerAdapter(new ContainerAdapterImpl(this));
this.foundation.init();
this.adapter.setDefaultOnChangeHandler(this.handleChange);
if (this.props.checked != null) {
this.foundation.setChecked(this.props.checked);
}
if (this.props.disabled != null) {
this.foundation.setDisabled(this.props.disabled);
}
}
public componentWillUnmount() {
this.foundation.destroy();
this.adapter.setContainerAdapter(new ContainerAdapter());
}
// Sync props and internal state
public componentWillReceiveProps(props) {
this.syncFoundation(props);
}
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
return this.state.foundationClasses.toJS();
}
private syncFoundation(props: MetaProps) {
if (props.checked != null && this.foundation.isChecked() !== this.props.checked) {
this.foundation.setChecked(props.checked);
}
if (props.disabled != null && this.foundation.isDisabled() !== this.props.disabled) {
this.foundation.setDisabled(props.disabled);
}
}
// Event handler
private handleChange = (_evt: React.ChangeEvent) => {
if (this.props.checked != null) {
if (this.foundation.isChecked() !== this.props.checked) {
// Checked state should not be changed by foundation
this.foundation.setChecked(this.props.checked);
}
}
}
}
class ContainerAdapterImpl extends ContainerAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public addClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.remove(className),
}));
}
public removeClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.add(className),
}));
}
public isChecked(): boolean | null {
return this.element.props.checked || null;
}
}
export default class Container extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"checked",
"disabled",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/radio/src/Default.tsx
================================================
import * as React from "react";
import Background from "./Background";
import Container from "./Container";
import InnerCircle from "./InnerCircle";
import NativeControl from "./NativeControl";
import OuterCircle from "./OuterCircle";
export type Props = {
inputId?: string,
name?: string,
value?: any,
onChange?: React.FormEventHandler,
checked?: boolean,
disabled?: boolean,
defaultChecked?: boolean,
};
/**
* Radio default composed component
*/
export default class Default extends React.Component {
public render() {
const {
inputId,
name,
value,
onChange,
checked,
disabled,
defaultChecked,
children: _children, // Ignore original children
...props,
} = this.props;
return (
);
}
}
================================================
FILE: packages/radio/src/InnerCircle.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__inner-circle`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class InnerCircle extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/radio/src/NativeControl.tsx
================================================
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as PropTypes from "prop-types";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { eventHandlerDecorator } from "@react-mdc/base/lib/util";
import { FoundationAdapter, NativeControlAdapter } from "./adapter";
import { BASE_CLASS_NAME } from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__native-control`;
export const propertyClassNames = {
PREFIX: CLASS_NAME,
};
// Maybe related to this
// https://github.com/Microsoft/TypeScript/issues/5938
export type ChangeEventHandler = React.FormEventHandler;
export type MetaProps = {
onChange?: ChangeEventHandler,
className?: string,
};
export type ChildProps = {
className?: string,
onChange?: ChangeEventHandler,
checked?: boolean,
};
export type Context = {
adapter: FoundationAdapter,
};
/**
* Radio input component
*/
export class Meta extends ClassNameMetaBase {
public static contextTypes = {
adapter: PropTypes.instanceOf(FoundationAdapter).isRequired,
};
public defaultOnChange: React.ChangeEventHandler;
public context: Context;
public componentDidMount() {
this.context.adapter.setNativeControlAdapter(new NativeControlAdapterImpl(this));
}
public componentWillUnmount() {
this.context.adapter.setNativeControlAdapter(new NativeControlAdapter());
}
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderProps(childProps: ChildProps) {
const {
onChange,
} = this.props;
return {
...super.renderProps(childProps),
checked: this.context.adapter.isChecked() || undefined,
onChange: (eventHandlerDecorator(this.handleChange)(onChange || null) as React.ChangeEventHandler),
};
}
private handleChange: React.ChangeEventHandler = (evt: React.ChangeEvent) => {
this.defaultOnChange(evt);
}
}
class NativeControlAdapterImpl extends NativeControlAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public getNativeControl(): Element | null {
return ReactDOM.findDOMNode(this.element);
}
public setDefaultOnChangeHandler(onChange: React.ChangeEventHandler) {
this.element.defaultOnChange = onChange;
}
}
// Input with type="radio" as default
function RadioInput(props: React.HTMLProps) {
return (
);
}
export default class NativeControl extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
"onChange",
];
}
protected getChildComponent(): React.SFC> {
return RadioInput;
}
}
================================================
FILE: packages/radio/src/OuterCircle.tsx
================================================
import * as React from "react";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import {
BASE_CLASS_NAME,
} from "./constants";
export const CLASS_NAME = `${BASE_CLASS_NAME}__outer-circle`;
export type MetaProps = {
className?: string,
};
export type ChildProps = {
className?: string,
};
export class Meta extends ClassNameMetaBase {
protected renderBaseClassName() {
return CLASS_NAME;
}
}
export default class OuterCircle extends DefaultComponentBase, MetaProps, {}> {
public static Meta = Meta;
protected getMetaComponent() {
return Meta;
}
protected getMetaPropNames() {
return [
"className",
];
}
protected getChildComponent() {
return "div";
}
}
================================================
FILE: packages/radio/src/__tests__/Background.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Background from "../Background";
describe("Background", () => {
it("Should have mdc-radio__background classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-radio__background")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/radio/src/__tests__/Container.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Container from "../Container";
describe("Container", () => {
it("Should have mdc-radio classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-radio")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/radio/src/__tests__/Default.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import Background from "../Background";
import Container from "../Container";
import Default from "../Default";
import InnerCircle from "../InnerCircle";
import NativeControl from "../NativeControl";
import OuterCircle from "../OuterCircle";
describe("Default", () => {
it("Should render common component composition", () => {
const wrapper = enzyme.shallow();
expect(wrapper.find(Background).exists()).toBeTruthy();
expect(wrapper.find(Container).exists()).toBeTruthy();
expect(wrapper.find(InnerCircle).exists()).toBeTruthy();
expect(wrapper.find(NativeControl).exists()).toBeTruthy();
expect(wrapper.find(OuterCircle).exists()).toBeTruthy();
});
});
================================================
FILE: packages/radio/src/__tests__/InnerCircle.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import InnerCircle from "../InnerCircle";
describe("InnerCircle", () => {
it("Should have mdc-radio__inner-circle classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-radio__inner-circle")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/radio/src/__tests__/NativeControl.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import * as PropTypes from "prop-types";
import { FoundationAdapter } from "../adapter";
import NativeControl from "../NativeControl";
describe("NativeControl", () => {
it("Should have mdc-radio__native-control classname", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.hasClass("mdc-radio__native-control")).toBeTruthy();
});
it("Should have input element as default component", () => {
const wrapper = enzyme.mount(, {
context: {
adapter: new FoundationAdapter(),
},
childContextTypes: {
adapter: PropTypes.instanceOf(FoundationAdapter),
},
});
expect(wrapper.find("input").exists()).toBeTruthy();
});
it("Should update foundation with props", () => {
// TODO: Add tests
});
});
================================================
FILE: packages/radio/src/__tests__/OuterCircle.spec.tsx
================================================
import "jest";
import * as React from "react";
import * as enzyme from "enzyme";
import OuterCircle from "../OuterCircle";
describe("OuterCircle", () => {
it("Should have mdc-radio__outer-circle classname", () => {
const wrapper = enzyme.mount();
expect(wrapper.hasClass("mdc-radio__outer-circle")).toBeTruthy();
});
it("Should have div element as default component", () => {
const wrapper = enzyme.mount();
expect(wrapper.find("div").exists()).toBeTruthy();
});
});
================================================
FILE: packages/radio/src/__tests__/adapter.spec.ts
================================================
import "jest";
import {
ContainerAdapter,
FoundationAdapter,
NativeControlAdapter,
} from "../adapter";
describe("ContainerAdapter", () => {
// TODO: Add adapter tests
it("Should have at least one test", () => {
});
});
describe("FoundationAdapter", () => {
// TODO: Add adapter tests
});
describe("NativeControlAdapter", () => {
// TODO: Add adapter tests
});
================================================
FILE: packages/radio/src/adapter.ts
================================================
/**
* Foundation adapters.
*/
/**
* Container adapter interface
* Default implementations are noop and returns meaningless value.
*/
export class ContainerAdapter {
public addClass(_className: string) {
}
public removeClass(_className: string) {
}
public isChecked(): boolean | null {
return null;
}
}
/**
* Native control adapter interface
* Default implementations are noop and returns meaningless value.
*/
export class NativeControlAdapter {
public getNativeControl(): Element | null {
return null;
}
public setDefaultOnChangeHandler(_onChange: React.ChangeEventHandler) {
}
}
/**
* Composite adapter for MDCRadioFoundation
*/
export class FoundationAdapter {
private nativeControlAdapter: NativeControlAdapter;
private containerAdapter: ContainerAdapter;
private defaultOnchangeHandler: React.ChangeEventHandler;
constructor() {
this.containerAdapter = new ContainerAdapter();
this.nativeControlAdapter = new NativeControlAdapter();
this.defaultOnchangeHandler = () => { };
}
public setContainerAdapter(containerAdapter: ContainerAdapter) {
this.containerAdapter = containerAdapter;
}
public setNativeControlAdapter(nativeControlAdapter: NativeControlAdapter) {
this.nativeControlAdapter = nativeControlAdapter;
this.nativeControlAdapter.setDefaultOnChangeHandler(this.defaultOnchangeHandler);
}
public addClass(className: string) {
this.containerAdapter.addClass(className);
}
public removeClass(className: string) {
this.containerAdapter.removeClass(className);
}
public getNativeControl(): Element | null {
return this.nativeControlAdapter.getNativeControl();
}
public setDefaultOnChangeHandler(onChange: React.ChangeEventHandler) {
this.nativeControlAdapter.setDefaultOnChangeHandler(onChange);
}
public isChecked(): boolean | null {
return this.containerAdapter.isChecked();
}
/**
* MDCFoundation accepts only object as adapter
* So we create object-proxy of instance.
*/
public toObject(): {} {
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const object = {};
keys.forEach((key: string) => {
object[key] = (...args) => this[key](...args);
});
return object;
}
}
================================================
FILE: packages/radio/src/constants.ts
================================================
export const BASE_CLASS_NAME = "mdc-radio";
================================================
FILE: packages/radio/src/index.ts
================================================
import Background from "./Background";
import Container from "./Container";
import Default from "./Default";
import InnerCircle from "./InnerCircle";
import NativeControl from "./NativeControl";
import OuterCircle from "./OuterCircle";
export default class Radio extends Container {
public static Background = Background;
public static InnerCircle = InnerCircle;
public static NativeControl = NativeControl;
public static OuterCircle = OuterCircle;
public static Default = Default;
}
================================================
FILE: packages/radio/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/radio/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/react-material-components-web/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/react-material-components-web/package.json
================================================
{
"name": "react-material-components-web",
"description": "React Components for Metarial Component Web",
"author": "Choi Geonu",
"version": "0.1.18",
"license": "MIT",
"keywords": [
"material components",
"material design",
"react"
],
"main": "src/index.js",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@react-mdc/base": "^0.1.12",
"@react-mdc/button": "^0.1.8",
"@react-mdc/card": "^0.1.9",
"@react-mdc/checkbox": "^0.1.12",
"@react-mdc/dialog": "^0.1.7",
"@react-mdc/drawer": "^0.1.13",
"@react-mdc/elevation": "^0.1.9",
"@react-mdc/fab": "^0.1.9",
"@react-mdc/form-field": "^0.1.8",
"@react-mdc/layout-grid": "^0.1.9",
"@react-mdc/list": "^0.1.9",
"@react-mdc/radio": "^0.1.12",
"@react-mdc/ripple": "^0.1.11",
"@react-mdc/switch": "^0.1.9",
"@react-mdc/textfield": "^0.1.11",
"@react-mdc/theme": "^0.1.8",
"@react-mdc/toolbar": "^0.1.11",
"@react-mdc/typography": "^0.1.8",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/react-material-components-web/src/index.ts
================================================
import * as base from "@react-mdc/base";
import * as button from "@react-mdc/button";
import * as card from "@react-mdc/card";
import * as checkbox from "@react-mdc/checkbox";
import * as drawer from "@react-mdc/drawer";
import * as elevation from "@react-mdc/elevation";
import * as fab from "@react-mdc/fab";
import * as formField from "@react-mdc/form-field";
import * as list from "@react-mdc/list";
import * as radio from "@react-mdc/radio";
import * as ripple from "@react-mdc/ripple";
import * as theme from "@react-mdc/theme";
import * as typography from "@react-mdc/typography";
// Export all
export {
base,
card,
drawer,
fab,
list,
theme,
button,
checkbox,
elevation,
formField,
radio,
ripple,
typography,
};
================================================
FILE: packages/react-material-components-web/tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"outDir": "./lib/",
"declaration": true,
"strictNullChecks": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"moduleResolution": "node",
"noEmitOnError": true
},
"include": [
"./src/**/*"
]
}
================================================
FILE: packages/react-material-components-web/tslint.json
================================================
{
"extends": [
"tslint:recommended",
"tslint-react"
],
"rules": {
// Common
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"prefer-const": [true, {"destructuring": "all"}],
"no-empty": false,
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"max-classes-per-file": [
false
],
// React
"jsx-no-lambda": true,
"jsx-alignment": false,
"jsx-boolean-value": false,
"jsx-no-multiline-js": false
}
}
================================================
FILE: packages/ripple/.npmignore
================================================
.git/
node_modules/
src/
yarn.lock
**/__mocks__/**
**/__tests__/**
================================================
FILE: packages/ripple/package.json
================================================
{
"name": "@react-mdc/ripple",
"description": "React wrapper of @material/ripple",
"version": "0.1.11",
"license": "MIT",
"main": "lib/index",
"typings": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/react-mdc/react-material-components-web.git"
},
"dependencies": {
"@material/ripple": "^0.6.0",
"@react-mdc/base": "^0.1.12",
"@types/classnames": "0.0.32",
"@types/react": "^15.0.18",
"classnames": "^2.2.5",
"immutable": "^3.8.1",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"devDependencies": {
"@types/enzyme": "^2.8.0",
"@types/jest": "^19.2.3",
"enzyme": "^2.8.2",
"jest": "^20.0.1",
"npm-run-all": "^4.0.2",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"ts-jest": "^20.0.3",
"tslint": "^5.2.0",
"tslint-react": "^3.0.0",
"typescript": "^2.4.1"
},
"scripts": {
"build": "npm-run-all build:ts lint",
"watch": "tsc --watch",
"prepublish": "npm run build",
"clean": "shx rm -rf lib/",
"lint": "tslint 'src/**/*.ts?(x)'",
"build:ts": "tsc",
"test": "jest"
},
"jest": {
"transform": {
".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
]
}
}
================================================
FILE: packages/ripple/src/Ripple.tsx
================================================
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
MDCRippleFoundation,
} from "@material/ripple/dist/mdc.ripple";
import {
Map,
OrderedMap,
OrderedSet,
Set,
} from "immutable";
import {
ClassNameMetaBase,
DefaultComponentBase,
} from "@react-mdc/base";
import { FoundationAdapter, RippleAdapter } from "./adapter";
import { getMatchesProperty, supportsCssVariables } from "./rippleUtil";
import { Color } from "./types";
import * as utils from "./utils";
import {
SURFACE_BASE_CLASS_NAME,
} from "./constants";
const MATCHES = getMatchesProperty(HTMLElement.prototype);
export const CLASS_NAME = SURFACE_BASE_CLASS_NAME;
export type MetaProps = {
unbounded?: boolean,
color?: Color,
className?: string,
};
export type ChildProps = {
className?: string,
};
export type State = {
foundationClasses: Set,
foundationCssVars: Map,
foundationEventListeners: Map>,
};
/**
* Ripple foundation component
*/
export class Meta extends ClassNameMetaBase {
public static defaultProps = {
unbounded: false,
};
public state: State = {
foundationClasses: OrderedSet(),
foundationCssVars: OrderedMap(),
foundationEventListeners: OrderedMap>(),
};
private adapter: FoundationAdapter;
private foundation: MDCRippleFoundation;
constructor(props) {
super(props);
this.adapter = new FoundationAdapter();
this.foundation = new MDCRippleFoundation(this.adapter.toObject());
}
// Exposed methods
public activate() {
this.foundation.activate();
}
public deactivate() {
this.foundation.deactivate();
}
public getDOMNode(): Element {
return ReactDOM.findDOMNode(this);
}
// Foundation lifecycle
public componentDidMount() {
this.adapter.setRippleAdapter(new RippleAdapterImpl(this));
this.foundation.init();
}
public componentWillUnmount() {
this.foundation.destroy();
this.adapter.setRippleAdapter(new RippleAdapter());
}
protected renderNativeDOMProps() {
return {
cssVariables: this.state.foundationCssVars.toJS(),
eventListeners: this.state.foundationEventListeners.toJS(),
};
}
protected renderBaseClassName() {
return CLASS_NAME;
}
protected renderClassValues() {
const classes: string[] = [];
if (this.props.color != null) {
classes.push(utils.classNameForColor(this.props.color));
}
return [
classes,
this.state.foundationClasses.toJS(),
];
}
}
class RippleAdapterImpl extends RippleAdapter {
private element: Meta;
constructor(element: Meta) {
super();
this.element = element;
}
public browserSupportsCssVars(): boolean {
return supportsCssVariables(window);
}
public isUnbounded(): boolean {
return this.element.props.unbounded || false;
}
public isSurfaceActive(): boolean {
return this.element.getDOMNode()[MATCHES](":active");
}
public addClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.add(className),
}));
}
public removeClass(className: string) {
this.element.setState((state) => ({
foundationClasses: state.foundationClasses.remove(className),
}));
}
public registerInteractionHandler(evtType: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
evtType,
OrderedSet(),
(x) => x.add(handler),
),
}));
}
public deregisterInteractionHandler(evtType: string, handler: EventListener) {
this.element.setState((state) => ({
foundationEventListeners: state.foundationEventListeners.update(
evtType,
OrderedSet(),
(x) => x.delete(handler),
),
}));
}
public registerResizeHandler(handler: EventListener) {
window.addEventListener("resize", handler);
}
public deregisterResizeHandler(handler: EventListener) {
window.removeEventListener("resize", handler);
}
public updateCssVariable(varName: string, value: string | null) {
if (value == null) {
this.element.setState((state) => ({
foundationCssVars: state.foundationCssVars.delete(varName),
}));
} else {
this.element.setState((state) => ({
foundationCssVars: state.foundationCssVars.set(varName, value),
}));
}
}
public computeBoundingRect(): ClientRect | null {
return this.element.getDOMNode().getBoundingClientRect();
}
public getWindowPageOffset(): { x: number, y: number } {
return { x: window.pageXOffset, y: window.pageYOffset };
}
}
export default class Ripple extends DefaultComponentBase