├── .babelrc ├── .codeclimate.yml ├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── components ├── Alert.jsx ├── AnimatedMenu.jsx ├── Block.jsx ├── Block │ └── Installments.jsx ├── Button.jsx ├── Checklist.jsx ├── ContextMenu.jsx ├── Dialog.jsx ├── Dropdown.jsx ├── Field.jsx ├── Fieldset.jsx ├── IconButton.jsx ├── Input.jsx ├── Installments.jsx ├── Label.jsx ├── Link.jsx ├── Loader │ ├── index.jsx │ └── styles.scss ├── Menu.jsx ├── PayButton.jsx ├── Preview.jsx ├── RadioGroup.jsx ├── Selector.jsx ├── Switch.jsx ├── Text.jsx ├── Theme.jsx ├── Tooltip.jsx ├── icons │ ├── AccountActivated.jsx │ ├── AllSet.jsx │ ├── Arrow.jsx │ ├── Cancel.jsx │ ├── Checkmark.jsx │ ├── CreditCard.jsx │ ├── Details.jsx │ ├── Done.jsx │ ├── Download.jsx │ ├── Error.jsx │ ├── ExtendDate.jsx │ ├── Items.jsx │ ├── KlarnaLogo.jsx │ ├── Letter.jsx │ ├── Logout.jsx │ ├── Mail.jsx │ ├── NotFound.jsx │ ├── OpenLetter.jsx │ ├── PadLock.jsx │ ├── Password.jsx │ ├── Person.jsx │ ├── Phone.jsx │ ├── Question.jsx │ ├── Remind.jsx │ ├── SMS.jsx │ ├── Time.jsx │ ├── Warning.jsx │ ├── Wrong.jsx │ ├── constants │ │ └── colors.es6 │ └── parts │ │ ├── Circle.jsx │ │ └── File.jsx ├── texts │ ├── Amount.jsx │ ├── Paragraph.jsx │ ├── PrimaryTitle.jsx │ ├── SecondaryTitle.jsx │ ├── Subtitle.jsx │ ├── TextLabel.jsx │ └── palette.es6 ├── themeable │ ├── Button.jsx │ ├── Checklist.jsx │ ├── Dropdown.jsx │ ├── Field.jsx │ ├── Installments.jsx │ ├── Link.jsx │ ├── Switch.jsx │ ├── Text.jsx │ └── texts │ │ ├── Paragraph.jsx │ │ ├── PrimaryTitle.jsx │ │ ├── SecondaryTitle.jsx │ │ └── Subtitle.jsx └── uncontrolled │ ├── Field.jsx │ ├── Input.jsx │ ├── Installments.jsx │ └── RadioGroup.jsx ├── docs └── patterns.md ├── example ├── Alerts.jsx ├── Blocks.jsx ├── Buttons.jsx ├── Checklists.jsx ├── Code.jsx ├── ContextMenus.jsx ├── Dialogs.jsx ├── Dropdowns.jsx ├── Fields.jsx ├── Icons.jsx ├── Inputs.jsx ├── Installments.jsx ├── Labels.jsx ├── Links.jsx ├── Loaders.jsx ├── Menus.jsx ├── Previews.jsx ├── RadioGroups.jsx ├── Selectors.jsx ├── Switches.jsx ├── Texts.jsx ├── Tooltips.jsx ├── examples.es6 ├── index.html ├── index.jsx └── index.scss ├── karma.conf.js ├── lib ├── combinations.es6 ├── compose.es6 ├── decorators │ ├── statefulFocus.jsx │ ├── statefulValue.jsx │ └── themeable.jsx ├── features │ ├── fieldStates │ │ └── index.es6 │ ├── inlinedIcon │ │ └── index.jsx │ ├── keyboardEvents │ │ └── index.es6 │ ├── programmaticFocus │ │ └── index.es6 │ └── stacking │ │ └── index.es6 ├── toObjectWithValue.es6 ├── validators.es6 └── values.es6 ├── package.json ├── propTypes ├── fieldSizeFraction.es6 └── validateSize.es6 ├── tests ├── Alert.spec.jsx ├── Amount.spec.jsx ├── Button.spec.jsx ├── Checklist.spec.jsx ├── Dialog.spec.jsx ├── Dropdown.spec.jsx ├── Field.spec.jsx ├── Fieldset.spec.jsx ├── Input.spec.jsx ├── Installments.spec.jsx ├── Label.spec.jsx ├── Link.spec.jsx ├── Loader.spec.jsx ├── Menu.spec.jsx ├── Paragraph.spec.jsx ├── PayButton.spec.jsx ├── Preview.spec.jsx ├── PrimaryTitle.spec.jsx ├── RadioGroup.spec.jsx ├── SecondaryTitle.spec.jsx ├── Selector.spec.jsx ├── Subtitle.spec.jsx ├── Switch.spec.jsx ├── TextLabel.spec.jsx ├── Tooltip.spec.jsx ├── decorators │ ├── statefulFocus.spec.jsx │ └── statefulValue.spec.jsx ├── describePalette.es6 ├── helpers.jsx ├── propTypes │ ├── fieldSizeFraction.spec.es6 │ └── validateSize.spec.es6 └── uncontrolled │ └── RadioGroup.spec.jsx ├── webpack.config.js └── webpack.config.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_paths: 2 | - example/**/* 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | end_of_line = lf 8 | insert_final_newline = true 9 | 10 | [*.{json,js,es6,jsx}] 11 | charset = utf-8 12 | indent_size = 2 13 | trim_trailing_whitespace = true 14 | 15 | [*.scss] 16 | charset = utf-8 17 | indent_size = 2 18 | trim_trailing_whitespace = true 19 | 20 | [Makefile] 21 | indent_style = tab 22 | 23 | [*.yml] 24 | indent_size = 2 25 | 26 | [package.json] 27 | indent_size = 2 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .idea/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | karma.conf.js 3 | webpack.config.js 4 | webpack.config.test.js 5 | tests 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | before_script: 5 | - npm install 6 | script: 7 | - npm run lint 8 | - BROWSER=SL_IE_10,SL_IE_11,SL_MAC_SAFARI_9_0,SL_Android_4_3,SL_Android_5_0,SL_IOS_9_2,PhantomJS npm test 9 | deploy: 10 | provider: npm 11 | email: ui-admin.e@klarna.com 12 | api_key: 13 | secure: TSbAVMniIdqjogdG99ZbNbv00vg6TWnWbVdQDTNauGMeRxyZkZnb4Bh6z0SvpXiBC+Cf+AxiAjoCVf+SwL6Rzsrlef6qrmJcDxdDmxserlHvG9krZiwinTNWPmdNOssGdymP3d5KL4jVWIS5QybEl/TaAodbYVsS66ns7SjSEjl3UVA+FeexDGglntKa9ruIqxS9XSnA0SbwW/CLY8U2tYhbaz9ITPmNr5pJtSRJbPavo664mIlP1ndYpIYmzZsbqt30/PVD81X1lGYAgP63i8vzsPWBvwPGFLYa32HoYTrIYbfdf8lHiwilAUL3RQsl0NC8Onez1bfnr8wScoEBArQ0ytvDkO6/IZoCne1ZHWTdUGxUPTHOZoaSYR9e83pLfoRaiX1qJer40SqU+1ocfVxcndufYK1312DlJdJj6oexGH05lQsMeHJJMFZ7snastxk2OX1+WPSmCW9FRciwT+giqiVPRTkyMSsGuoQBQeDolTpSKu1dWWFKhSI2sXZcmdHGvOEFgYJZlzzh8JeVmoCBfXx9urCUEEjUf8X6OxvN1/gHc/OiY0t0mIsXIwohl+M6qJaA2ewRmPKZLvpVhvkgmwuj2ug2azwCyJbojt2AN7hfrrdkxO8Sws8DPyBTsm44bF0iJ25Ysi3wSAJiS4AgJIiWgW0H8VtwJMhXGc0= 14 | on: 15 | branch: master 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.16.0 2 | 3 | - Revise support for customizable/themeable border-radius. 4 | Currently we can only support vertically-stacked fields. 5 | - Add support for themeable Dropdown. 6 | 7 | # 0.15.0 8 | 9 | - Add support for wide `Dialog` 10 | 11 | # 0.14.1 12 | 13 | - Fixed missing borderColor on themeable Installments 14 | 15 | # 0.14.0 16 | 17 | - Add Theme and themeable for several components 18 | 19 | # 0.13.2 20 | 21 | - Fix dynamic styling of input border 22 | 23 | # 0.13.0 24 | 25 | - Update `Dialog` component 26 | 27 | # 0.12.11 28 | 29 | - Add alert box 30 | 31 | # 0.12.10 32 | 33 | - Spread props in Dropdown options to be able to set hidden and disabled 34 | - Set `describe` and `it` as globals for standard in the package.json 35 | 36 | # 0.12.9 37 | 38 | - Fix horizontal positioning of the Loader inside a Button 39 | 40 | # 0.12.8 41 | 42 | - Add dynamic styling support to Field, Switch, Button and Link 43 | 44 | # 0.12.7 45 | 46 | - Fix 'aria-labelledby' attribute on SVG in Checklist component 47 | 48 | # 0.12.6 49 | 50 | - Update Switch "checked" state in `componentWillReceiveProps`, based on the "checked" prop. 51 | 52 | # 0.12.5 53 | 54 | - Add `Dropdown` component 55 | 56 | # 0.12.4 57 | 58 | - Add a field with details icon inside 59 | 60 | # 0.12.3 61 | 62 | - Icons can receive `className` prop 63 | 64 | # 0.12.2 65 | 66 | - Add Cancel icon 67 | 68 | # 0.12.1 69 | 70 | - Add `viewBox` property to all icons 71 | 72 | # 0.12.0 73 | 74 | - Update all icons to use the CSS classes 75 | - Remove the icon HOC 76 | 77 | # 0.11.0 78 | 79 | - Update CSS components with default margins to 0 80 | - Add the `margins` flag to text components so text margins can be re enabled 81 | - Use components consistently throughout the showroom 82 | 83 | # 0.10.2 84 | 85 | - Add pad lock icon 86 | - Updated the css component version to support ie9 fields 87 | 88 | # 0.10.1 89 | 90 | - Change prop type of menu label to node 91 | 92 | # 0.10.0 93 | 94 | - Add legal variation of the Switch component 95 | 96 | # 0.9.0 97 | 98 | - Add Checklist component 99 | 100 | # 0.8.6 101 | 102 | - Added white loaders 103 | 104 | # 0.8.4 105 | 106 | - Merge 0.7.5 into 0.8.3 107 | 108 | # 0.8.0 109 | 110 | - New approach using decorators for adding the Uncontrolled versions 111 | Complete rewrite of the icon approach (needs review since some icons are clearly - meant for buttons) 112 | - Update naming convention (from stateless/stateful to controlled/uncontrolled) 113 | Add support for Field and Input stacking (buggy in the case of Input, but that's - probably due to ui-css-components shortcomings) 114 | - Add support for declarative focus in Field and Input 115 | - Cleanup composition of features in Field and Input 116 | - Added propType for fractional props 117 | 118 | # 0.7.5 119 | 120 | - Using ui-css-components to v5.6.5 to get some minor margins improvements 121 | 122 | # 0.7.4 123 | 124 | - Selector onChange callback called with an object instead of id 125 | 126 | # 0.7.2 & 0.7.3 127 | 128 | - Hotfixes for AnimatedMenu's transition 129 | 130 | # 0.7.0 131 | 132 | - Borderless input fields 133 | - Adds 'giant' input type 134 | - Controlled/uncontrolled inputs 135 | 136 | # 0.6.0 137 | 138 | - Adds white versions for texts 139 | 140 | # 0.5.4 141 | 142 | - Fixes ContextMenu prop types 143 | 144 | # 0.5.3 145 | 146 | - Adds ContextMenu 147 | 148 | # 0.5.2 149 | 150 | - Adds Selector with check icon 151 | 152 | # 0.5.1 153 | 154 | - Adds IconButtons with correct palette 155 | 156 | # 0.5.0 157 | 158 | - Adds LICENSE and open source 159 | 160 | # 0.4.0 161 | 162 | - Moves all icons from ui-illustrations 163 | - Use these icons for the Field 164 | 165 | # 0.3.0 166 | 167 | - Adds Switch error state 168 | 169 | # 0.2.5 170 | 171 | - Adds Label 172 | - Adds Dialog.Icon 173 | 174 | # 0.2.4 175 | 176 | - Adds Dialog 177 | 178 | # 0.2.3 179 | 180 | - Quickfix for AnimatedMenu 181 | 182 | # 0.2.2 183 | 184 | - Adds Tooltip with border 185 | 186 | # 0.2.1 187 | 188 | - Adds AnimatedMenu 189 | 190 | # 0.2.0 191 | 192 | - Adds Block 193 | - Adds Ammount 194 | - Reorganize Text components 195 | - Refactos Menus (breaking change) 196 | 197 | # 0.1.4 198 | 199 | - Adds TabMenu 200 | 201 | # 0.1.3 202 | 203 | - Adds RadioGroup & Stateteful.RadioGroup 204 | 205 | # 0.1.2 206 | 207 | - Adds Tooltip 208 | 209 | # 0.1.1 210 | 211 | - Adds Preview 212 | - Adds Switch 213 | 214 | # 0.1.0 215 | 216 | - Adds PayButton 217 | - Adds Field 218 | - Adds Icon 219 | - Adds Loader 220 | - Adds Button 221 | - Setup 222 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Source code is licensed under Apache 2.0 2 | http://www.apache.org/licenses/LICENSE-2.0 3 | 4 | All icons and images are licensed under Creative Commons Attribution-NoDerivatives 4.0 5 | http://creativecommons.org/licenses/by-nd/4.0/ 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | clean: 4 | rm -rf node_modules 5 | 6 | install: 7 | npm install 8 | 9 | lint: 10 | npm run lint 11 | 12 | dev: 13 | npm start 14 | 15 | test: 16 | npm test 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Klarna UI React Components 2 | 3 | [![Build Status](https://travis-ci.org/klarna/ui-react-components.svg)](https://travis-ci.org/klarna/ui-react-components) 4 | [![Code Climate](https://codeclimate.com/github/klarna/ui-react-components/badges/gpa.svg)](https://codeclimate.com/github/klarna/ui-react-components) 5 | [![npm version](https://img.shields.io/npm/v/@klarna/ui-react-components.svg?maxAge=2592000)](https://www.npmjs.com/package/@klarna/ui-react-components) 6 | 7 | ## Note: This project and its sister project [klarna/ui-css-components](https://github.com/klarna/ui-css-components) are deprecated. Use [@klarna/ui](https://github.com/klarna/ui) instead. 8 | 9 | This library is a [React](https://facebook.github.io/react/) wrapper on top of [ui-css-components](https://github.com/klarna/ui-css-components). 10 | 11 | ## Install 12 | 13 | ```sh 14 | npm install @klarna/ui-react-components --save 15 | ``` 16 | 17 | This package doesn't have a build, so you must have a Babel pipeline to use it. The minimal set of loaders is: 18 | 19 | ```javascript 20 | test: /\.(jsx|es6)$/ 21 | loader: 'babel' 22 | 23 | test: /\.scss$/, 24 | loaders: [ 25 | 'style', 26 | 'css?modules,localIdentName=[local]', 27 | 'sass' 28 | ] 29 | 30 | test: /\.(jpe?g|png|gif|svg|ico|eot|woff|ttf|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, 31 | loader: 'file' // or url 32 | ``` 33 | 34 | You can see more in the project's `webpack.config.js`. 35 | 36 | ## Run locally 37 | 38 | > To run the project, NPM 3+ is required. 39 | 40 | To run the showroom locally: 41 | 42 | ```sh 43 | npm install 44 | npm start 45 | ``` 46 | 47 | Open [localhost:7777](http://localhost:7777/). 48 | 49 | ## Using locally 50 | 51 | Most of the time you'll want to change things in `ui-react-components` and see how they reflect in your project. To do that without having to push and publish versions, you need to create a global symlink from `ui-react-components` and then use this symlink in your project. 52 | 53 | So first, create the global symlink by doing: 54 | 55 | ```sh 56 | cd path/to/ui-react-components 57 | npm link 58 | ``` 59 | 60 | Then go to your project and: 61 | 62 | ``` 63 | npm link @klarna/ui-react-components 64 | UV_THREADPOOL_SIZE=100 npm start 65 | ``` 66 | 67 | This uses the global symlink of `ui-react-components` that points to our local git copy. Replace `npm start` with the command you use to start your app, if you use something different. 68 | 69 | The `UV_THREADPOOL_SIZE=100` solves a problem you may encounter with symlinks when importing Sass files [https://github.com/jtangelder/sass-loader/issues/100](https://github.com/jtangelder/sass-loader/issues/100). 70 | 71 | ### Running the tests in PhantomJS locally 72 | 73 | ```sh 74 | npm test 75 | ``` 76 | 77 | ## Running the tests in different browsers 78 | 79 | ### Prerequisites 80 | 81 | First install the required npm packages. 82 | ```sh 83 | npm install karma-chrome-launcher 84 | npm install karma-firefox-launcher 85 | npm install karma-ie-launcher 86 | npm install karma-safari-launcher 87 | npm install karma-webdriver-launcher 88 | ``` 89 | 90 | ### Run the tests on OS X 91 | ```sh 92 | BROWSER=PhantomJS,Chrome,Safari,Firefox npm test 93 | ``` 94 | 95 | ### Run the tests on Windows 96 | ```sh 97 | BROWSER=PhantomJS,Chrome,Firefox npm test 98 | ``` 99 | 100 | ## License 101 | 102 | Please check the [LICENSE](LICENSE) file. 103 | 104 | ## Contributing 105 | 106 | Make sure: 107 | 108 | 1. Your contribution is aligned with the styleguide. 109 | 2. Your contribution doesn't break the grid. To avoid that, always use the `$grid` variable to define your sizes, as in `line-height: ($grid * 4)`. As a rule of thumb, if your element total height (sum of content, paddings, margins, etc.) has an integer multiple of `$grid` you should be good. 110 | 3. Your code is linted: `npm run lint`. 111 | 4. It works in the major browsers, the simplest way is to spawn [ngrok](https://ngrok.com/) and use the cloud service of your choice. Else, you can download IE virtual machines for VirtualBox using `curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | env IEVMS_VERSIONS="9" bash`. 112 | 113 | Then: 114 | 115 | 1. Send a PR to GitHub. 116 | 2. Once approved: 117 | 1. Update the version using `npm version` (tag will have `v` prefix) & update `CHANGELOG.md`. 118 | 2. Merge to master and push (with the new tag as well). 119 | 120 | Travis will take care of publishing your new version to npm. 121 | -------------------------------------------------------------------------------- /components/Alert.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/alert.scss' 4 | 5 | export function Title ({ children, className, styles, ...remainingProps }) { 6 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 7 | const cls = classNames('cui__alert__title', className) 8 | 9 | return ( 10 |

11 | {children} 12 |

13 | ) 14 | } 15 | 16 | Title.propTypes = { 17 | children: PropTypes.node, 18 | className: PropTypes.string, 19 | design: PropTypes.oneOf(Alert.designs), 20 | styles: PropTypes.object 21 | } 22 | 23 | Title.defaultProps = { 24 | styles: {} 25 | } 26 | 27 | export function Paragraph ({ children, className, styles, ...remainingProps }) { 28 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 29 | const cls = classNames('cui__alert__paragraph', className) 30 | 31 | return ( 32 |

33 | {children} 34 |

35 | ) 36 | } 37 | 38 | Paragraph.propTypes = { 39 | children: PropTypes.node, 40 | className: PropTypes.string, 41 | design: PropTypes.oneOf(Alert.designs), 42 | styles: PropTypes.object 43 | } 44 | 45 | Paragraph.defaultProps = { 46 | styles: {} 47 | } 48 | 49 | export default function Alert ({ children, className, styles, design, ...remainingProps }) { 50 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 51 | const cls = classNames(`cui__alert--${design}`, className) 52 | 53 | return ( 54 |
55 | {children} 56 |
57 | ) 58 | } 59 | 60 | Alert.designs = ['error'] 61 | 62 | Alert.defaultProps = { 63 | design: 'error', 64 | styles: {} 65 | } 66 | 67 | Alert.propTypes = { 68 | children: PropTypes.node, 69 | className: PropTypes.string, 70 | design: PropTypes.oneOf(Alert.designs), 71 | styles: PropTypes.object 72 | } 73 | -------------------------------------------------------------------------------- /components/AnimatedMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Motion, spring } from 'react-motion' 3 | import Menu from './Menu' 4 | import styles from '@klarna/ui-css-components/src/components/tab-menu.scss' 5 | 6 | const AnimatedSelectedBar = ({ width, left }) => ( 7 | 8 | {(style) =>
} 9 | 10 | ) 11 | 12 | export default class AnimatedMenu extends React.Component { 13 | constructor (props) { 14 | super(props) 15 | 16 | this.state = { width: 0, left: 0 } 17 | } 18 | 19 | update () { 20 | const tab = document.getElementById(`${this.props.name}-${this.props.selected}-tab`) 21 | const { left, width } = tab.getBoundingClientRect() 22 | const parentLeft = tab.parentNode.getBoundingClientRect().left 23 | 24 | this.setState({ width, left: (left - parentLeft) }) 25 | } 26 | 27 | componentDidUpdate (prevProps) { 28 | if (this.props.options.length !== prevProps.options.length || this.props.selected !== prevProps.selected) { 29 | this.update() 30 | } 31 | } 32 | 33 | componentDidMount () { 34 | setTimeout(this.update.bind(this), 0) 35 | } 36 | 37 | render () { 38 | return ( 39 | 40 | {this.state.width > 0 && } 41 | 42 | ) 43 | } 44 | } 45 | 46 | AnimatedMenu.propTypes = { 47 | onChange: PropTypes.func.isRequired, 48 | selected: PropTypes.string.isRequired 49 | } 50 | -------------------------------------------------------------------------------- /components/Block.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/block.scss' 4 | 5 | export default function Block ({className, blue, children, styles, ...remainingProps}) { 6 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 7 | const cls = classNames('cui__block', { blue }, className) 8 | 9 | return ( 10 |
11 | {children} 12 |
13 | ) 14 | } 15 | 16 | Block.propTypes = { 17 | blue: PropTypes.bool, 18 | children: PropTypes.node, 19 | className: PropTypes.string, 20 | styles: PropTypes.object 21 | } 22 | -------------------------------------------------------------------------------- /components/Block/Installments.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/block.scss' 4 | 5 | const baseClass = 'cui__block--installments' 6 | 7 | const classes = { 8 | title: `${baseClass}__title`, 9 | value: `${baseClass}__value`, 10 | valueContent: `${baseClass}__value__content`, 11 | valueContentClarification: `${baseClass}__value__content__clarification`, 12 | valueTitle: `${baseClass}__value__title`, 13 | values: `${baseClass}__values` 14 | } 15 | 16 | export function Main ({ className, children, styles, ...props }) { 17 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 18 | 19 | return ( 20 |
21 | {children} 22 |
23 | ) 24 | } 25 | 26 | Main.displayName = 'BlockInstallments.Main' 27 | 28 | Main.propTypes = { 29 | className: PropTypes.string, 30 | children: PropTypes.node, 31 | styles: PropTypes.object 32 | } 33 | 34 | export function Title ({ className, children, styles, ...props }) { 35 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 36 | 37 | return ( 38 |
39 | {children} 40 |
41 | ) 42 | } 43 | 44 | Title.displayName = 'BlockInstallments.Title' 45 | 46 | Title.propTypes = { 47 | className: PropTypes.string, 48 | children: PropTypes.node, 49 | styles: PropTypes.object 50 | } 51 | 52 | export function Content ({ className, children, styles, ...props }) { 53 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 54 | 55 | return ( 56 |
57 | {children} 58 |
59 | ) 60 | } 61 | 62 | Content.displayName = 'BlockInstallments.Content' 63 | 64 | Content.propTypes = { 65 | className: PropTypes.string, 66 | children: PropTypes.node, 67 | styles: PropTypes.object 68 | } 69 | 70 | export function Value ({ className, clarification, children, title, styles, value, ...props }) { 71 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 72 | 73 | return ( 74 |
75 |
76 | {title} 77 |
78 | 79 |
80 | {value} 81 | {clarification && ( 82 | 83 | {clarification} 84 | 85 | )} 86 |
87 |
88 | ) 89 | } 90 | 91 | Value.displayName = 'BlockInstallments.Value' 92 | 93 | Value.propTypes = { 94 | clarification: PropTypes.string, 95 | className: PropTypes.string, 96 | children: PropTypes.node, 97 | title: PropTypes.string.isRequired, 98 | value: PropTypes.string.isRequired, 99 | styles: PropTypes.object 100 | } 101 | -------------------------------------------------------------------------------- /components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Loader from './Loader' 3 | import classNamesBind from 'classnames/bind' 4 | import defaultStyles from '@klarna/ui-css-components/src/components/button.scss' 5 | import parseColor from 'parse-color' 6 | 7 | export default function Button (props) { 8 | const { 9 | children, 10 | className, 11 | customize, 12 | design, 13 | disabled, 14 | loading, 15 | size, 16 | styles, 17 | success, 18 | ...remainingProps } = props 19 | 20 | const content = 21 | success && '✔' || !loading && children 22 | 23 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 24 | 25 | const cls = classNames(`cui__button--${design}`, size, { 26 | 'is-disabled': disabled, 27 | 'is-loading': loading, 28 | 'dynamic-styling': customize 29 | }, className) 30 | 31 | const labelCls = { 32 | label: classNames('cui__button__label'), 33 | alt: classNames('cui__button__label--alt') 34 | } 35 | 36 | const isDisabled = (loading || success || disabled) 37 | 38 | const loaderColor = getLoaderColor( 39 | design, 40 | (customize || {}).textColor, 41 | (customize || {}).backgroundColor 42 | ) 43 | 44 | const dynamicRenderer = design === 'primary' 45 | ? renderDynamicallyStyledPrimaryButton 46 | : renderDynamicallyStyledSecondaryButton 47 | 48 | return customize 49 | ? dynamicRenderer(content, cls, labelCls, isDisabled, loading, loaderColor, customize, {...remainingProps}) 50 | : renderButton(content, cls, isDisabled, loading, loaderColor, {...remainingProps}) 51 | } 52 | 53 | Button.defaultProps = { 54 | design: 'primary', 55 | loading: false, 56 | success: false, 57 | disabled: false 58 | } 59 | 60 | Button.designs = ['primary', 'secondary'] 61 | Button.sizes = ['small', 'big'] 62 | 63 | Button.propTypes = { 64 | children: PropTypes.node, 65 | className: PropTypes.string, 66 | customize: PropTypes.shape({ 67 | textColor: PropTypes.string.isRequired, 68 | backgroundColor: PropTypes.string.isRequired, 69 | borderRadius: PropTypes.string.isRequired 70 | }), 71 | design: PropTypes.oneOf(Button.designs), 72 | size: PropTypes.oneOf(Button.sizes), 73 | loading: PropTypes.bool, 74 | success: PropTypes.bool, 75 | disabled: PropTypes.bool, 76 | styles: PropTypes.object 77 | } 78 | 79 | const renderButton = (content, classNames, disabled, loading, loaderColor, remainingProps) => ( 80 | 83 | ) 84 | 85 | const renderDynamicallyStyledPrimaryButton = (content, classNames, labelClassNames, disabled, loading, loaderColor, {textColor, backgroundColor, borderRadius}, remainingProps) => ( 86 | 96 | ) 97 | 98 | const renderDynamicallyStyledSecondaryButton = (content, classNames, labelClassNames, disabled, loading, loaderColor, {textColor, backgroundColor, borderRadius}, remainingProps) => ( 99 | 117 | ) 118 | 119 | const getLoaderColor = (design, textColor, backgroundColor) => { 120 | if (textColor && backgroundColor) { 121 | const { rgb } = parseColor(design === 'primary' ? textColor : backgroundColor) 122 | return rgb 123 | } 124 | 125 | return design === 'primary' ? 'white' : 'blue' 126 | } 127 | -------------------------------------------------------------------------------- /components/Checklist.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/checklist.scss' 4 | 5 | export default function Checklist ({ chromeless, className, children, customize, styles }) { 6 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 7 | 8 | const dynamicStyles = customize 9 | ? { 10 | borderRadius: customize.borderRadius, 11 | borderColor: customize.borderColor 12 | } 13 | : undefined 14 | 15 | return ( 16 | 21 | ) 22 | } 23 | 24 | Checklist.propTypes = { 25 | className: PropTypes.string, 26 | children: PropTypes.node, 27 | chromeless: PropTypes.bool, 28 | styles: PropTypes.object, 29 | customize: PropTypes.shape({ 30 | borderColor: PropTypes.string.isRequired, 31 | borderRadius: PropTypes.string.isRequired 32 | }) 33 | } 34 | 35 | Checklist.defaultProps = { 36 | styles: {} 37 | } 38 | 39 | Checklist.Item = ({ className, children, customize, styles }) => { 40 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 41 | const iconDynamicStyles = customize 42 | ? { 43 | stroke: customize.strokeColor 44 | } 45 | : undefined 46 | 47 | return ( 48 |
  • 50 | 55 | 56 | 57 | {children} 58 |
  • 59 | ) 60 | } 61 | 62 | Checklist.Item.displayName = 'Checklist.Item' 63 | 64 | Checklist.Item.propTypes = { 65 | className: PropTypes.string, 66 | children: PropTypes.node, 67 | styles: PropTypes.object, 68 | customize: PropTypes.shape({ 69 | strokeColor: PropTypes.string.isRequired 70 | }) 71 | } 72 | 73 | Checklist.Item.defaultProps = { 74 | styles: {} 75 | } 76 | -------------------------------------------------------------------------------- /components/ContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/context-menu.scss' 4 | 5 | const baseClass = 'cui__context-menu' 6 | 7 | const ContextMenu = ({ className, children, styles, ...props }) => { 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | 10 | return ( 11 |
      12 | {children} 13 |
    14 | ) 15 | } 16 | 17 | ContextMenu.Link = ({ className, children, styles, ...props }) => { 18 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 19 | 20 | return ( 21 |
  • 22 | 23 | {children} 24 | 25 |
  • 26 | ) 27 | } 28 | ContextMenu.Link.displayName = 'ContextMenu.Link' 29 | 30 | ContextMenu.Item = ({ className, children, styles, ...props }) => { 31 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 32 | 33 | return ( 34 |
  • 35 | {children} 36 |
  • 37 | ) 38 | } 39 | ContextMenu.Item.displayName = 'ContextMenu.Item' 40 | 41 | ContextMenu.propTypes = ContextMenu.Link.propTypes = ContextMenu.Item.propTypes = { 42 | className: PropTypes.string, 43 | children: PropTypes.node, 44 | styles: PropTypes.object 45 | } 46 | 47 | ContextMenu.Separator = ({ className, styles, ...props }) => { 48 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 49 | 50 | return ( 51 |
  • 52 | ) 53 | } 54 | ContextMenu.Separator.displayName = 'ContextMenu.Separator' 55 | ContextMenu.Separator.propTypes = { 56 | className: PropTypes.string, 57 | styles: PropTypes.object 58 | } 59 | 60 | ContextMenu.Icon = ({ className, children, styles }) => { 61 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 62 | 63 | return ( 64 | React.cloneElement(React.Children.only(children), { 65 | className: classNames(`${baseClass}__icon`, className) 66 | }) 67 | ) 68 | } 69 | ContextMenu.Icon.displayName = 'ContextMenu.Icon' 70 | ContextMenu.Icon.propTypes = { 71 | className: PropTypes.string, 72 | children: PropTypes.element, 73 | styles: PropTypes.object 74 | } 75 | 76 | export default ContextMenu 77 | -------------------------------------------------------------------------------- /components/Dialog.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/dialog.scss' 4 | 5 | export default function Dialog ({ children, className, styles, ...props }) { 6 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 7 | 8 | return ( 9 |
    10 | {children} 11 |
    12 | ) 13 | } 14 | 15 | Dialog.propTypes = { 16 | children: PropTypes.node, 17 | className: PropTypes.string, 18 | styles: PropTypes.object 19 | } 20 | 21 | Dialog.Icon = ({ children, className, styles, ...props }) => { 22 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 23 | 24 | return ( 25 |
    26 | {children} 27 |
    28 | ) 29 | } 30 | 31 | Dialog.Icon.propTypes = { 32 | children: PropTypes.node, 33 | className: PropTypes.string, 34 | styles: PropTypes.object 35 | } 36 | 37 | Dialog.Icon.displayName = 'Dialog.Icon' 38 | 39 | Dialog.Content = ({ children, className, styles, ...props }) => { 40 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 41 | 42 | return ( 43 |
    44 |
    45 | {children} 46 |
    47 |
    48 | ) 49 | } 50 | 51 | Dialog.Content.displayName = 'Dialog.Content' 52 | 53 | Dialog.Content.propTypes = { 54 | children: PropTypes.node, 55 | className: PropTypes.string, 56 | styles: PropTypes.object 57 | } 58 | 59 | Dialog.Footer = ({ children, className, styles, ...props }) => { 60 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 61 | 62 | return ( 63 |
    65 |
    67 | {children} 68 |
    69 |
    70 | ) 71 | } 72 | 73 | Dialog.Footer.displayName = 'Dialog.Footer' 74 | 75 | Dialog.Footer.propTypes = { 76 | children: PropTypes.node, 77 | className: PropTypes.string, 78 | styles: PropTypes.object 79 | } 80 | 81 | Dialog.Overlay = ({ children, className, show, wide, styles, ...props }) => { 82 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 83 | 84 | return ( 85 |
    87 |
    89 |
    91 | {children} 92 |
    93 |
    94 |
    95 | ) 96 | } 97 | 98 | Dialog.Overlay.propTypes = { 99 | children: PropTypes.node, 100 | className: PropTypes.string, 101 | show: PropTypes.bool, 102 | styles: PropTypes.object 103 | } 104 | -------------------------------------------------------------------------------- /components/Fieldset.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes} from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/field.scss' 4 | 5 | export default function Fieldset (props) { 6 | const { 7 | className, 8 | children, 9 | styles, 10 | ...remainingProps } = props 11 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 12 | 13 | const cls = classNames('cui__fieldset', className) 14 | 15 | return ( 16 |
    {children}
    17 | ) 18 | } 19 | 20 | Fieldset.propTypes = { 21 | children: PropTypes.node, 22 | className: PropTypes.string, 23 | styles: PropTypes.object 24 | } 25 | -------------------------------------------------------------------------------- /components/IconButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | 5 | const iconPropTypes = { 6 | className: PropTypes.string, 7 | color: PropTypes.oneOf(['gray', 'inverse']), 8 | styles: PropTypes.object 9 | } 10 | 11 | export const BackButton = ({ className, color, styles, ...props }) => { 12 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 13 | 14 | return ( 15 | 21 | 25 | 26 | ) 27 | } 28 | 29 | export const CloseButton = ({ className, color, styles, ...props }) => { 30 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 31 | 32 | return ( 33 | 39 | 41 | 43 | 44 | ) 45 | } 46 | 47 | export const HamburgerButton = ({ className, color, styles, ...props }) => { 48 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 49 | 50 | return ( 51 | 57 | {[8, 13, 18].map((y) => 58 | 62 | )} 63 | 64 | ) 65 | } 66 | 67 | export const OptionsButton = ({ className, color, styles, ...props }) => { 68 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 69 | 70 | return ( 71 | 75 | {[7, 13, 19].map((y) => 76 | 80 | )} 81 | 82 | ) 83 | } 84 | 85 | export const SearchButton = ({ className, color, styles, ...props }) => { 86 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 87 | 88 | return ( 89 | 95 | 98 | 101 | 102 | ) 103 | } 104 | 105 | BackButton.propTypes = CloseButton.propTypes = HamburgerButton.propTypes = OptionsButton.propTypes = SearchButton.propTypes = iconPropTypes 106 | -------------------------------------------------------------------------------- /components/Input.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/input.scss' 4 | import * as programmaticFocus from '../lib/features/programmaticFocus' 5 | import * as fieldStates from '../lib/features/fieldStates' 6 | import * as inlinedIcon from '../lib/features/inlinedIcon' 7 | import { position, size } from '../lib/features/stacking' 8 | import { handleKeyDown } from '../lib/features/keyboardEvents' 9 | 10 | export default class Input extends Component { 11 | componentDidMount () { 12 | programmaticFocus.maybeFocus(document)(this.props.focus, this.refs.input) 13 | } 14 | 15 | componentDidUpdate () { 16 | programmaticFocus.maybeFocus(document)(this.props.focus, this.refs.input) 17 | } 18 | 19 | render () { 20 | const { 21 | big, 22 | className, 23 | centered, 24 | disabled, 25 | giant, 26 | icon, 27 | label, 28 | loading, 29 | onBlur, 30 | onChange, 31 | onClick, 32 | onFocus, 33 | square, 34 | value, 35 | styles, 36 | ...props 37 | } = this.props 38 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 39 | 40 | const classes = { 41 | field: classNames( 42 | (icon ? 'cui__input--icon' : 'cui__input'), { 43 | big, 44 | giant, 45 | 'is-centered': centered, 46 | 'is-filled': value != null && value !== '', 47 | 'is-loading': loading, 48 | square 49 | }, 50 | fieldStates.getClassName(this.props), 51 | programmaticFocus.getClassName(this.props), 52 | size.getClassName(this.props), 53 | position.getClassName(this.props), 54 | className), 55 | label: classNames( 56 | icon 57 | ? 'cui__input--icon__label' 58 | : 'cui__input__label' 59 | ), 60 | input: classNames( 61 | icon 62 | ? 'cui__input--icon__input' 63 | : 'cui__input__input' 64 | ) 65 | } 66 | 67 | return ( 68 |
    72 | { 73 | inlinedIcon.renderInlinedIcon(this.props, { 74 | icon: classNames('cui__input--icon__icon'), 75 | fill: classNames('cui__input--icon__icon__fill'), 76 | stroke: classNames('cui__input--icon__icon__stroke') 77 | }) 78 | } 79 | 80 | 81 | 82 | 93 |
    94 | ) 95 | } 96 | } 97 | 98 | Input.defaultProps = { 99 | big: false, 100 | centered: false, 101 | giant: false, 102 | loading: false, 103 | onChange: function () {}, 104 | ...inlinedIcon.defaultProps, 105 | ...fieldStates.defaultProps, 106 | ...position.defaultProps, 107 | ...handleKeyDown.defaultProps, 108 | ...size.defaultProps 109 | } 110 | 111 | Input.propTypes = { 112 | big: PropTypes.bool, 113 | centered: PropTypes.bool, 114 | giant: PropTypes.bool, 115 | loading: PropTypes.bool, 116 | label: PropTypes.string.isRequired, 117 | onBlur: PropTypes.func, 118 | onChange: PropTypes.func, 119 | onClick: PropTypes.func, 120 | onFocus: PropTypes.func, 121 | value: PropTypes.string, 122 | styles: PropTypes.object, 123 | ...inlinedIcon.propTypes, 124 | ...fieldStates.propTypes, 125 | ...handleKeyDown.propTypes, 126 | ...position.propTypes, 127 | ...programmaticFocus.propTypes, 128 | ...size.propTypes 129 | } 130 | -------------------------------------------------------------------------------- /components/Label.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/label.scss' 4 | 5 | export default function Label ({ 6 | children, 7 | className, 8 | design, 9 | outline, 10 | inverted, 11 | styles, 12 | ...props 13 | }) { 14 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 15 | const cls = classNames('cui__label', design, className, { 16 | outline, 17 | inverted 18 | }) 19 | 20 | return ( 21 | 22 | {children} 23 | 24 | ) 25 | } 26 | 27 | Label.designs = [ 28 | 'information', 29 | 'warning', 30 | 'notice', 31 | 'success', 32 | 'brown', 33 | 'purple', 34 | 'light-blue', 35 | 'ultramarine', 36 | 'yellow', 37 | 'grey', 38 | 'black' 39 | ] 40 | 41 | Label.propTypes = { 42 | children: PropTypes.node, 43 | className: PropTypes.string, 44 | design: PropTypes.oneOf(Label.designs), 45 | inverted: PropTypes.bool, 46 | outline: PropTypes.bool, 47 | styles: PropTypes.object 48 | } 49 | -------------------------------------------------------------------------------- /components/Link.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/link.scss' 4 | import palette from './texts/palette' 5 | 6 | export default function Link ({className, color, children, styles, customize, ...props}) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | const cls = classNames('cui__link', color, className, { 9 | 'dynamic-styling': customize 10 | }) 11 | 12 | const customizedStyles = customize 13 | ? { 14 | color: customize.textColor 15 | } 16 | : undefined 17 | 18 | return ( 19 | 20 | {children} 21 | 22 | ) 23 | } 24 | 25 | Link.propTypes = { 26 | children: PropTypes.node, 27 | className: PropTypes.string, 28 | color: PropTypes.oneOf(palette), 29 | customize: PropTypes.shape({ 30 | textColor: PropTypes.string.isRequired 31 | }), 32 | styles: PropTypes.object 33 | } 34 | -------------------------------------------------------------------------------- /components/Loader/index.jsx: -------------------------------------------------------------------------------- 1 | // Based on http://codepen.io/skuester/pen/Hejbz 2 | 3 | import React, { PropTypes } from 'react' 4 | import classNamesBind from 'classnames/bind' 5 | import defaultStyles from './styles.scss' 6 | 7 | const colors = { 8 | blue: [0, 116, 200], 9 | white: [255, 255, 255] 10 | } 11 | colors.default = [158, 158, 160] 12 | 13 | const sizes = { 14 | big: 30, 15 | small: 15, 16 | tiny: 10 17 | } 18 | sizes.default = 20 19 | 20 | const gradients = [ 21 | {x1: 0, y1: '0', x2: '0', y2: '1'}, 22 | {x1: '1', y1: '0', x2: '0', y2: '1'}, 23 | {x1: '1', y1: '1', x2: '0', y2: '0'}, 24 | {x1: '0', y1: '1', x2: '0', y2: '0'}, 25 | {x1: '0', y1: '1', x2: '1', y2: '0'}, 26 | {x1: '0', y1: '0', x2: '1', y2: '1'} 27 | ] 28 | 29 | export default function Loader ({ className, color, inline, size, styles }) { 30 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 31 | 32 | const _color = Array.isArray(color) ? color : colors[color] || colors.default 33 | const _size = sizes[size] || sizes.default 34 | const step = 0.2 35 | const stroke = 2 36 | const half = _size / 2 37 | const quarter = _size / 4 38 | const corner = _size * 0.433 39 | 40 | return ( 41 | 42 | 43 | { 44 | gradients.map((props, index) => ( 45 | 46 | 47 | 48 | 49 | )) 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ) 62 | } 63 | 64 | Loader.propTypes = { 65 | className: PropTypes.string, 66 | color: PropTypes.oneOfType([ 67 | PropTypes.oneOf(Object.keys(colors)), 68 | PropTypes.array 69 | ]), 70 | inline: PropTypes.bool, 71 | size: PropTypes.oneOf(Object.keys(sizes)), 72 | styles: PropTypes.object 73 | } 74 | -------------------------------------------------------------------------------- /components/Loader/styles.scss: -------------------------------------------------------------------------------- 1 | @keyframes spin { 2 | 0% { transform: rotate(0deg); } 3 | 100% { transform: rotate(360deg); } 4 | } 5 | 6 | .loader { 7 | animation: spin .8s linear infinite; 8 | display: block; 9 | margin: 0; 10 | } 11 | 12 | .inline { 13 | margin: 0 auto; 14 | } 15 | -------------------------------------------------------------------------------- /components/Menu.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import segmentedStyles from '@klarna/ui-css-components/src/components/segmentedcontrol.scss' 4 | import tabStyles from '@klarna/ui-css-components/src/components/tab-menu.scss' 5 | 6 | const designs = { 7 | tab: { 8 | baseClass: 'cui__tab-menu', 9 | styles: tabStyles 10 | }, 11 | segmented: { 12 | baseClass: 'cui__segmentedcontrol', 13 | styles: segmentedStyles 14 | } 15 | } 16 | 17 | export default function Menu (props) { 18 | const { 19 | className, 20 | design, 21 | tabDisplay, 22 | options, 23 | name, 24 | onChange, 25 | onClick, 26 | selected, 27 | selectable, 28 | white, 29 | children, 30 | ...remainingProps } = props 31 | 32 | const { baseClass, styles } = designs[design] 33 | const classNames = classNamesBind.bind(styles) 34 | 35 | const cls = classNames(baseClass, tabDisplay, className, { 36 | 'is-selectable': selectable, 37 | white 38 | }) 39 | 40 | const tabStyle = tabDisplay === 'static' 41 | ? {width: (100 / options.length) + '%'} 42 | : {} 43 | 44 | const items = options.map(({ key, label }, index) => { 45 | const id = `${name}-${key}` 46 | 47 | const tabClass = classNames(`${baseClass}__button`, { 48 | left: index === 0, 49 | center: index > 0 && index < options.length - 1, 50 | right: index === options.length - 1 51 | }) 52 | 53 | return ( 54 |
    55 | onChange(key))} 61 | defaultChecked={key === selected} /> 62 | 70 |
    71 | ) 72 | }) 73 | 74 | return ( 75 |
    76 | {children} 77 | {items} 78 |
    79 | ) 80 | } 81 | 82 | Menu.defaultProps = { 83 | design: 'tab', 84 | tabDisplay: 'fluid', 85 | selectable: true 86 | } 87 | 88 | Menu.designs = ['tab', 'segmented'] 89 | Menu.tabDisplays = ['fluid', 'static'] 90 | 91 | Menu.optionsSchema = PropTypes.shape({ 92 | label: PropTypes.node.isRequired, 93 | key: PropTypes.string.isRequired 94 | }) 95 | 96 | Menu.propTypes = { 97 | options: PropTypes.arrayOf(Menu.optionsSchema).isRequired, 98 | className: PropTypes.string, 99 | design: PropTypes.oneOf(Menu.designs), 100 | tabDisplay: PropTypes.oneOf(Menu.tabDisplays), 101 | onChange: PropTypes.func, 102 | onClick: PropTypes.func, 103 | name: PropTypes.string.isRequired, 104 | selected: PropTypes.string, 105 | selectable: PropTypes.bool, 106 | white: PropTypes.bool, 107 | children: PropTypes.node 108 | } 109 | -------------------------------------------------------------------------------- /components/PayButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import defaultStyles from '@klarna/ui-css-components/src/components/button.scss' 3 | import Button from './Button' 4 | import classNamesBind from 'classnames/bind' 5 | 6 | export default function PayButton ({ 7 | price, 8 | children, 9 | className, 10 | loading, 11 | styles, 12 | ...remainingProps 13 | }) { 14 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 15 | const cls = classNames(loading || 'has-price', className) 16 | 17 | return ( 18 | 24 | ) 25 | } 26 | 27 | PayButton.defaultProps = Button.defaultProps 28 | 29 | PayButton.propTypes = { 30 | ...Button.propTypes, 31 | price: PropTypes.string.isRequired 32 | } 33 | -------------------------------------------------------------------------------- /components/Preview.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/preview.scss' 4 | 5 | export default function Preview ({ className, children, styles }) { 6 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 7 | const cls = classNames('cui__preview', className) 8 | 9 | return ( 10 |
    11 |
    12 | {children} 13 |
    14 |
    15 | ) 16 | } 17 | 18 | Preview.propTypes = { 19 | className: PropTypes.string, 20 | children: PropTypes.node, 21 | styles: PropTypes.object 22 | } 23 | 24 | export function PreviewTitle ({ children, className, styles }) { 25 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 26 | const cls = classNames('cui__preview__title', className) 27 | 28 | return ( 29 |

    30 | {children} 31 |

    32 | ) 33 | } 34 | 35 | PreviewTitle.propTypes = { 36 | className: PropTypes.string, 37 | children: PropTypes.node, 38 | styles: PropTypes.object 39 | } 40 | 41 | export function PreviewLink ({ children, className, styles, ...remainingProps }) { 42 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 43 | const cls = classNames('cui__preview__footer__link', className) 44 | 45 | return ( 46 |
    47 | 48 | {children} 49 | 50 |
    51 | ) 52 | } 53 | 54 | PreviewLink.propTypes = { 55 | className: PropTypes.string, 56 | children: PropTypes.node, 57 | styles: PropTypes.object 58 | } 59 | -------------------------------------------------------------------------------- /components/RadioGroup.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/dropdown.scss' 4 | 5 | export default function RadioGroup (props) { 6 | const { selected, onChange, className, data, styles, ...remainingProps } = props 7 | const baseClass = 'cui__dropdown--radio' 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | const cls = classNames(baseClass, className) 10 | 11 | const options = data.map(({id, label, description}) => { 12 | const optionClass = classNames(`${baseClass}__option`, { 13 | 'is-selected': id === selected 14 | }) 15 | const labelClass = classNames(`${baseClass}__option__heading`) 16 | const descriptionClass = classNames(`${baseClass}__option__description`) 17 | 18 | return ( 19 |
    onChange(id)}> 20 | 21 | {description &&
    {description}
    } 22 |
    23 | ) 24 | }) 25 | 26 | return ( 27 |
    28 | {options} 29 |
    30 | ) 31 | } 32 | 33 | RadioGroup.optionSchema = PropTypes.shape({ 34 | id: PropTypes.any.isRequired, 35 | label: PropTypes.string.isRequired, 36 | description: PropTypes.node 37 | }) 38 | 39 | RadioGroup.propTypes = { 40 | // Allows any type to be an id, as long as it is comparable 41 | selected: React.PropTypes.any.isRequired, 42 | onChange: React.PropTypes.func.isRequired, 43 | className: PropTypes.string, 44 | data: PropTypes.arrayOf(RadioGroup.optionSchema).isRequired, 45 | styles: PropTypes.object 46 | } 47 | -------------------------------------------------------------------------------- /components/Selector.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/selector.scss' 4 | import Checkmark from './icons/Checkmark' 5 | 6 | export default function Selector ({ 7 | selected, 8 | onChange, 9 | className, 10 | data, 11 | styles, 12 | ...remainingProps 13 | }) { 14 | const baseClass = 'cui__selector--direct' 15 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 16 | const cls = classNames(baseClass, 'title', className) 17 | 18 | const options = data.map((d) => { 19 | const optionClass = classNames(`${baseClass}__item`) 20 | const labelClass = classNames(`${baseClass}__label`) 21 | const iconClass = classNames(`${baseClass}__icon`) 22 | 23 | return ( 24 |
    onChange(d)}> 25 |
    {d.label}
    26 | {d.id === selected ? : null} 27 |
    28 | ) 29 | }) 30 | 31 | return ( 32 |
    33 | {options} 34 |
    35 | ) 36 | } 37 | 38 | Selector.optionSchema = PropTypes.shape({ 39 | id: PropTypes.any.isRequired, 40 | label: PropTypes.string.isRequired 41 | }) 42 | 43 | Selector.propTypes = { 44 | // Allows any type to be an id, as long as it is comparable 45 | selected: React.PropTypes.any.isRequired, 46 | onChange: React.PropTypes.func.isRequired, 47 | className: PropTypes.string, 48 | data: PropTypes.arrayOf(Selector.optionSchema).isRequired, 49 | styles: PropTypes.object 50 | } 51 | -------------------------------------------------------------------------------- /components/Text.jsx: -------------------------------------------------------------------------------- 1 | export { default as Amount } from './texts/Amount' 2 | export { default as Paragraph } from './texts/Paragraph' 3 | export { default as PrimaryTitle } from './texts/PrimaryTitle' 4 | export { default as SecondaryTitle } from './texts/SecondaryTitle' 5 | export { default as Subtitle } from './texts/Subtitle' 6 | export { default as TextLabel } from './texts/TextLabel' 7 | -------------------------------------------------------------------------------- /components/Theme.jsx: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react' 2 | import { getContextualizer } from 'react-context-props' 3 | 4 | const Theme = getContextualizer({ 5 | customizations: PropTypes.shape({ 6 | color_button: PropTypes.string, 7 | color_button_text: PropTypes.string, 8 | color_checkbox: PropTypes.string, 9 | color_checkbox_checkmark: PropTypes.string, 10 | color_text: PropTypes.string, 11 | color_header: PropTypes.string, 12 | color_link: PropTypes.string, 13 | color_border: PropTypes.string, 14 | color_border_selected: PropTypes.string, 15 | color_details: PropTypes.string, 16 | color_text_secondary: PropTypes.string, 17 | radius_border: PropTypes.string 18 | }) 19 | }) 20 | 21 | Theme.displayName = 'Theme' 22 | 23 | export default Theme 24 | -------------------------------------------------------------------------------- /components/Tooltip.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/tooltip.scss' 4 | 5 | export default function Tooltip ({ className, arrow, children, border, styles }) { 6 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 7 | const cls = classNames('cui__tooltip', arrow, className, { border }) 8 | 9 | return ( 10 |
    11 | {children} 12 |
    13 | ) 14 | } 15 | 16 | Tooltip.arrows = [ 17 | 'top', 'top-left', 'top-right', 18 | 'bottom', 'bottom-left', 'bottom-right', 19 | 'left', 'left-top', 'left-bottom', 20 | 'right', 'right-top', 'right-bottom' 21 | ] 22 | 23 | Tooltip.propTypes = { 24 | className: PropTypes.string, 25 | arrow: PropTypes.oneOf(Tooltip.arrows), 26 | children: PropTypes.node, 27 | border: PropTypes.bool, 28 | styles: PropTypes.object 29 | } 30 | -------------------------------------------------------------------------------- /components/icons/AccountActivated.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function AccountActivated ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 15 | 18 | 19 | 20 | 22 | 24 | 26 | 28 | 29 | 30 | ) 31 | } 32 | 33 | AccountActivated.defaultProps = { 34 | color: 'blue', 35 | styles: {} 36 | } 37 | 38 | AccountActivated.propTypes = { 39 | color: PropTypes.oneOf(colors), 40 | styles: PropTypes.object 41 | } 42 | -------------------------------------------------------------------------------- /components/icons/AllSet.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function AllSet ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 22 | 23 | ) 24 | } 25 | 26 | AllSet.defaultProps = { 27 | color: 'blue', 28 | styles: {} 29 | } 30 | 31 | AllSet.propTypes = { 32 | color: PropTypes.oneOf(colors), 33 | styles: PropTypes.object 34 | } 35 | -------------------------------------------------------------------------------- /components/icons/Arrow.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Arrow ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 18 | 19 | ) 20 | } 21 | 22 | Arrow.defaultProps = { 23 | color: 'blue', 24 | styles: {} 25 | } 26 | 27 | Arrow.propTypes = { 28 | color: PropTypes.oneOf(colors), 29 | styles: PropTypes.object 30 | } 31 | -------------------------------------------------------------------------------- /components/icons/Cancel.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Cancel ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 20 | 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | Cancel.defaultProps = { 30 | color: 'blue', 31 | styles: {} 32 | } 33 | 34 | Cancel.propTypes = { 35 | color: PropTypes.oneOf(colors), 36 | styles: PropTypes.object 37 | } 38 | -------------------------------------------------------------------------------- /components/icons/Checkmark.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Checkmark ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 16 | 19 | 20 | ) 21 | } 22 | 23 | Checkmark.defaultProps = { 24 | color: 'blue', 25 | styles: {} 26 | } 27 | 28 | Checkmark.propTypes = { 29 | color: PropTypes.oneOf(colors), 30 | styles: PropTypes.object 31 | } 32 | -------------------------------------------------------------------------------- /components/icons/CreditCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Amex from './cards/Amex' 3 | import Discover from './cards/Discover' 4 | import Maestro from './cards/Maestro' 5 | import Mastercard from './cards/Mastercard' 6 | import Visa from './cards/Visa' 7 | import VisaElectron from './cards/VisaElectron' 8 | 9 | const brands = { 10 | amex: Amex, 11 | discover: Discover, 12 | maestro: Maestro, 13 | master: Mastercard, 14 | visa: Visa, 15 | visaelectron: VisaElectron 16 | } 17 | 18 | const CreditCard = ({ brand, ...props }) => { 19 | const Brand = brands[brand] 20 | if (!Brand) { 21 | return {brand} 22 | } 23 | 24 | return 25 | } 26 | 27 | CreditCard.brands = Object.keys(brands) 28 | 29 | CreditCard.propTypes = { 30 | brand: PropTypes.oneOf(CreditCard.brands) 31 | } 32 | 33 | export default CreditCard 34 | -------------------------------------------------------------------------------- /components/icons/Details.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Details ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | 33 | Details.defaultProps = { 34 | color: 'blue', 35 | styles: {} 36 | } 37 | 38 | Details.propTypes = { 39 | color: PropTypes.oneOf(colors), 40 | styles: PropTypes.object 41 | } 42 | -------------------------------------------------------------------------------- /components/icons/Done.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | import Circle from './parts/Circle.jsx' 6 | 7 | export default function Done ({ color, styles, className, ...props }) { 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | 10 | return ( 11 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | Done.defaultProps = { 26 | color: 'blue', 27 | styles: {} 28 | } 29 | 30 | Done.propTypes = { 31 | color: PropTypes.oneOf(colors), 32 | styles: PropTypes.object 33 | } 34 | -------------------------------------------------------------------------------- /components/icons/Download.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Download ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 22 | 23 | ) 24 | } 25 | 26 | Download.defaultProps = { 27 | color: 'blue', 28 | styles: {} 29 | } 30 | 31 | Download.propTypes = { 32 | color: PropTypes.oneOf(colors), 33 | styles: PropTypes.object 34 | } 35 | -------------------------------------------------------------------------------- /components/icons/Error.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | import Circle from './parts/Circle.jsx' 6 | 7 | export default function Error ({ color, styles, className, ...props }) { 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | 10 | return ( 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | Error.defaultProps = { 26 | color: 'blue', 27 | styles: {} 28 | } 29 | 30 | Error.propTypes = { 31 | color: PropTypes.oneOf(colors), 32 | styles: PropTypes.object 33 | } 34 | -------------------------------------------------------------------------------- /components/icons/ExtendDate.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function ExtendDate ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 30 | 31 | ) 32 | } 33 | 34 | ExtendDate.defaultProps = { 35 | color: 'blue', 36 | styles: {} 37 | } 38 | 39 | ExtendDate.propTypes = { 40 | color: PropTypes.oneOf(colors), 41 | styles: PropTypes.object 42 | } 43 | -------------------------------------------------------------------------------- /components/icons/Items.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Items ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 15 | {[6.5, 11.5, 16.5].map((y) => ( 16 | 19 | 20 | 21 | 22 | ))} 23 | 24 | ) 25 | } 26 | 27 | Items.defaultProps = { 28 | color: 'blue', 29 | styles: {} 30 | } 31 | 32 | Items.propTypes = { 33 | color: PropTypes.oneOf(colors), 34 | styles: PropTypes.object 35 | } 36 | -------------------------------------------------------------------------------- /components/icons/KlarnaLogo.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Icon, { icon } from './Icon.jsx' 3 | 4 | const KlarnaLogo = (hexColor, {width, ...otherProps}) => { 5 | const height = width / KlarnaLogo.ratio 6 | 7 | return ( 8 | 14 | 15 | 16 | ) 17 | } 18 | 19 | KlarnaLogo.ratio = 101 / 28 20 | KlarnaLogo.propTypes = { 21 | width: PropTypes.number 22 | } 23 | 24 | KlarnaLogo.defaultProps = { 25 | width: 63, 26 | color: 'blue' 27 | } 28 | 29 | export default icon(KlarnaLogo) 30 | -------------------------------------------------------------------------------- /components/icons/Letter.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Letter ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | Letter.defaultProps = { 26 | color: 'blue', 27 | styles: {} 28 | } 29 | 30 | Letter.propTypes = { 31 | color: PropTypes.oneOf(colors), 32 | styles: PropTypes.object 33 | } 34 | -------------------------------------------------------------------------------- /components/icons/Logout.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Logout ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 22 | 23 | ) 24 | } 25 | 26 | Logout.defaultProps = { 27 | color: 'blue', 28 | styles: {} 29 | } 30 | 31 | Logout.propTypes = { 32 | color: PropTypes.oneOf(colors), 33 | styles: PropTypes.object 34 | } 35 | -------------------------------------------------------------------------------- /components/icons/Mail.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Mail ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | Mail.defaultProps = { 25 | color: 'blue', 26 | styles: {} 27 | } 28 | 29 | Mail.propTypes = { 30 | color: PropTypes.oneOf(colors), 31 | styles: PropTypes.object 32 | } 33 | -------------------------------------------------------------------------------- /components/icons/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | import File from './parts/File.jsx' 6 | 7 | export default function NotFound ({ color, styles, className, ...props }) { 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | 10 | return ( 11 | 15 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | NotFound.defaultProps = { 33 | color: 'blue', 34 | styles: {} 35 | } 36 | 37 | NotFound.propTypes = { 38 | color: PropTypes.oneOf(colors), 39 | styles: PropTypes.object 40 | } 41 | -------------------------------------------------------------------------------- /components/icons/OpenLetter.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function OpenLetter ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 22 | 23 | ) 24 | } 25 | 26 | OpenLetter.defaultProps = { 27 | color: 'blue', 28 | styles: {} 29 | } 30 | 31 | OpenLetter.propTypes = { 32 | color: PropTypes.oneOf(colors), 33 | styles: PropTypes.object 34 | } 35 | -------------------------------------------------------------------------------- /components/icons/PadLock.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function PadLock ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | PadLock.defaultProps = { 26 | color: 'blue', 27 | styles: {} 28 | } 29 | 30 | PadLock.propTypes = { 31 | color: PropTypes.oneOf(colors), 32 | styles: PropTypes.object 33 | } 34 | -------------------------------------------------------------------------------- /components/icons/Password.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Password ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | Password.defaultProps = { 25 | color: 'blue', 26 | styles: {} 27 | } 28 | 29 | Password.propTypes = { 30 | color: PropTypes.oneOf(colors), 31 | styles: PropTypes.object 32 | } 33 | -------------------------------------------------------------------------------- /components/icons/Person.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Person ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 20 | 23 | 25 | 26 | 27 | ) 28 | } 29 | 30 | Person.defaultProps = { 31 | color: 'blue', 32 | styles: {} 33 | } 34 | 35 | Person.propTypes = { 36 | color: PropTypes.oneOf(colors), 37 | styles: PropTypes.object 38 | } 39 | -------------------------------------------------------------------------------- /components/icons/Phone.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Phone ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 16 | 18 | 19 | 20 | ) 21 | } 22 | 23 | Phone.defaultProps = { 24 | color: 'blue', 25 | styles: {} 26 | } 27 | 28 | Phone.propTypes = { 29 | color: PropTypes.oneOf(colors), 30 | styles: PropTypes.object 31 | } 32 | -------------------------------------------------------------------------------- /components/icons/Question.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Question ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 20 | 26 | 29 | 31 | 32 | 33 | ) 34 | } 35 | 36 | Question.defaultProps = { 37 | color: 'blue', 38 | styles: {} 39 | } 40 | 41 | Question.propTypes = { 42 | color: PropTypes.oneOf(colors), 43 | styles: PropTypes.object 44 | } 45 | -------------------------------------------------------------------------------- /components/icons/Remind.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Remind ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 23 | 25 | 26 | 27 | ) 28 | } 29 | 30 | Remind.defaultProps = { 31 | color: 'blue', 32 | styles: {} 33 | } 34 | 35 | Remind.propTypes = { 36 | color: PropTypes.oneOf(colors), 37 | styles: PropTypes.object 38 | } 39 | -------------------------------------------------------------------------------- /components/icons/SMS.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function SMS ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 23 | 25 | 27 | 28 | 29 | ) 30 | } 31 | 32 | SMS.defaultProps = { 33 | color: 'blue', 34 | styles: {} 35 | } 36 | 37 | SMS.propTypes = { 38 | color: PropTypes.oneOf(colors), 39 | styles: PropTypes.object 40 | } 41 | -------------------------------------------------------------------------------- /components/icons/Time.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | import Circle from './parts/Circle.jsx' 6 | 7 | export default function Time ({ color, styles, className, ...props }) { 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | 10 | return ( 11 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | Time.defaultProps = { 26 | color: 'blue', 27 | styles: {} 28 | } 29 | 30 | Time.propTypes = { 31 | color: PropTypes.oneOf(colors), 32 | styles: PropTypes.object 33 | } 34 | -------------------------------------------------------------------------------- /components/icons/Warning.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | 6 | export default function Warning ({ color, styles, className, ...props }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | 9 | return ( 10 | 14 | 17 | 19 | 21 | 23 | 24 | 25 | ) 26 | } 27 | 28 | Warning.defaultProps = { 29 | color: 'blue', 30 | styles: {} 31 | } 32 | 33 | Warning.propTypes = { 34 | color: PropTypes.oneOf(colors), 35 | styles: PropTypes.object 36 | } 37 | -------------------------------------------------------------------------------- /components/icons/Wrong.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/illustration.scss' 4 | import colors from './constants/colors' 5 | import File from './parts/File.jsx' 6 | 7 | export default function Wrong ({ color, styles, className, ...props }) { 8 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 9 | 10 | return ( 11 | 15 | 18 | 19 | 21 | 23 | 25 | 26 | 27 | ) 28 | } 29 | 30 | Wrong.defaultProps = { 31 | color: 'blue', 32 | styles: {} 33 | } 34 | 35 | Wrong.propTypes = { 36 | color: PropTypes.oneOf(colors), 37 | styles: PropTypes.object 38 | } 39 | -------------------------------------------------------------------------------- /components/icons/constants/colors.es6: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'blue', 3 | 'black', 4 | 'gray', 5 | 'success', 6 | 'error', 7 | 'warning', 8 | 'inverse' 9 | ] 10 | -------------------------------------------------------------------------------- /components/icons/parts/Circle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Circle (props) { 4 | return ( 5 | 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /components/icons/parts/File.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function File (props) { 4 | return ( 5 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /components/texts/Amount.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/text.scss' 4 | import palette from './palette' 5 | 6 | export default function Amount ({ children, className, color, styles, ...remainingProps }) { 7 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 8 | const cls = classNames('cui__amount-text', color, className) 9 | 10 | return ( 11 |

    12 | {children} 13 |

    14 | ) 15 | } 16 | 17 | Amount.defaultProps = { 18 | color: 'black', 19 | styles: {} 20 | } 21 | 22 | Amount.propTypes = { 23 | color: PropTypes.oneOf(palette), 24 | children: PropTypes.node, 25 | className: PropTypes.string, 26 | styles: PropTypes.object 27 | } 28 | -------------------------------------------------------------------------------- /components/texts/Paragraph.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/text.scss' 4 | import palette from './palette' 5 | 6 | export default function Paragraph ({ 7 | children, 8 | className, 9 | color, 10 | condensed, 11 | margins, 12 | design, 13 | styles, 14 | ...props 15 | }) { 16 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 17 | const cls = classNames( 18 | `cui__paragraph--${design}`, 19 | color, 20 | className, 21 | { 22 | condensed, 23 | 'default-margins': margins 24 | } 25 | ) 26 | 27 | return ( 28 |

    29 | {children} 30 |

    31 | ) 32 | } 33 | 34 | Paragraph.designs = ['primary', 'secondary', 'legal'] 35 | 36 | Paragraph.defaultProps = { 37 | color: undefined, 38 | condensed: false, 39 | margins: false, 40 | design: 'primary', 41 | styles: {} 42 | } 43 | 44 | Paragraph.propTypes = { 45 | children: PropTypes.node, 46 | className: PropTypes.string, 47 | color: PropTypes.oneOf(palette), 48 | condensed: PropTypes.bool, 49 | margins: PropTypes.bool, 50 | design: PropTypes.oneOf(Paragraph.designs), 51 | styles: PropTypes.object 52 | } 53 | -------------------------------------------------------------------------------- /components/texts/PrimaryTitle.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/text.scss' 4 | import palette from './palette' 5 | 6 | export default function PrimaryTitle ({ 7 | children, 8 | className, 9 | color, 10 | margins, 11 | small, 12 | strong, 13 | styles, 14 | ...props 15 | }) { 16 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 17 | 18 | const cls = classNames( 19 | 'cui__title--primary', 20 | color, 21 | className, 22 | { 23 | 'default-margins': margins, 24 | small, 25 | strong 26 | } 27 | ) 28 | 29 | return ( 30 |

    31 | {children} 32 |

    33 | ) 34 | } 35 | 36 | PrimaryTitle.defaultProps = { 37 | color: 'black', 38 | small: false, 39 | strong: false, 40 | margins: false, 41 | styles: {} 42 | } 43 | 44 | PrimaryTitle.propTypes = { 45 | children: PropTypes.node, 46 | className: PropTypes.string, 47 | color: PropTypes.oneOf(palette), 48 | margins: PropTypes.bool, 49 | small: PropTypes.bool, 50 | strong: PropTypes.bool, 51 | styles: PropTypes.object 52 | } 53 | -------------------------------------------------------------------------------- /components/texts/SecondaryTitle.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/text.scss' 4 | import palette from './palette' 5 | 6 | export default function SecondaryTitle ({ 7 | className, 8 | color, 9 | condensed, 10 | children, 11 | margins, 12 | styles, 13 | ...props 14 | }) { 15 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 16 | 17 | const cls = classNames( 18 | 'cui__title--secondary', 19 | color, 20 | className, 21 | { 22 | condensed, 23 | 'default-margins': margins 24 | } 25 | ) 26 | 27 | return ( 28 |

    29 | {children} 30 |

    31 | ) 32 | } 33 | 34 | SecondaryTitle.defaultProps = { 35 | color: 'black', 36 | condensed: false, 37 | margins: false, 38 | styles: {} 39 | } 40 | 41 | SecondaryTitle.propTypes = { 42 | children: PropTypes.node, 43 | className: PropTypes.string, 44 | color: PropTypes.oneOf(palette), 45 | condensed: PropTypes.bool, 46 | margins: PropTypes.bool, 47 | styles: PropTypes.object 48 | } 49 | -------------------------------------------------------------------------------- /components/texts/Subtitle.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/text.scss' 4 | import palette from './palette' 5 | 6 | export default function Subtitle ({ 7 | children, 8 | className, 9 | color, 10 | condensed, 11 | margins, 12 | styles, 13 | ...props 14 | }) { 15 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 16 | 17 | const cls = classNames( 18 | 'cui__subtitle', 19 | color, 20 | className, 21 | { 22 | condensed, 23 | 'default-margins': margins 24 | } 25 | ) 26 | 27 | return ( 28 |

    29 | {children} 30 |

    31 | ) 32 | } 33 | 34 | Subtitle.defaultProps = { 35 | color: 'black', 36 | condensed: false, 37 | margins: false, 38 | styles: {} 39 | } 40 | 41 | Subtitle.propTypes = { 42 | children: PropTypes.node, 43 | className: PropTypes.string, 44 | color: PropTypes.oneOf(palette), 45 | margins: PropTypes.bool, 46 | styles: PropTypes.object 47 | } 48 | -------------------------------------------------------------------------------- /components/texts/TextLabel.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import classNamesBind from 'classnames/bind' 3 | import defaultStyles from '@klarna/ui-css-components/src/components/text.scss' 4 | 5 | export default function TextLabel ({ 6 | className, 7 | children, 8 | margins, 9 | styles, 10 | ...remainingProps 11 | }) { 12 | const classNames = classNamesBind.bind({ ...defaultStyles, ...styles }) 13 | const cls = classNames( 14 | 'cui__text-label', 15 | className, 16 | { 17 | 'default-margins': margins 18 | } 19 | ) 20 | 21 | return ( 22 |

    {children}

    23 | ) 24 | } 25 | 26 | TextLabel.defaultProps = { 27 | margins: false, 28 | styles: {} 29 | } 30 | 31 | TextLabel.propTypes = { 32 | children: PropTypes.node, 33 | className: PropTypes.string, 34 | margins: PropTypes.bool, 35 | styles: PropTypes.object 36 | } 37 | -------------------------------------------------------------------------------- /components/texts/palette.es6: -------------------------------------------------------------------------------- 1 | export default ['black', 'blue', 'white'] 2 | -------------------------------------------------------------------------------- /components/themeable/Button.jsx: -------------------------------------------------------------------------------- 1 | import Button from '../Button' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | // TODO: borderRadius works for PrimaryButton, but not for SecondaryButton 5 | // because of the inner border-radius not matching up. 6 | export default themeable(Button, (customizations, props) => ({ 7 | ...props, 8 | customize: { 9 | ...props.customize, 10 | backgroundColor: customizations.color_button, 11 | borderRadius: customizations.radius_border, 12 | textColor: customizations.color_button_text 13 | } 14 | })) 15 | -------------------------------------------------------------------------------- /components/themeable/Checklist.jsx: -------------------------------------------------------------------------------- 1 | import Checklist from '../Checklist' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | const ThemeableChecklist = themeable(Checklist, (customizations, props) => ({ 5 | ...props, 6 | customize: { 7 | ...props.customize, 8 | borderColor: customizations.color_border, 9 | borderRadius: customizations.radius_border 10 | } 11 | })) 12 | 13 | const ThemeableChecklistItem = themeable(Checklist.Item, (customizations, props) => ({ 14 | ...props, 15 | customize: { 16 | ...props.customize, 17 | strokeColor: customizations.color_detail, 18 | borderRadius: customizations.radius_border 19 | } 20 | })) 21 | 22 | ThemeableChecklist.Item = ThemeableChecklistItem 23 | 24 | export default ThemeableChecklist 25 | -------------------------------------------------------------------------------- /components/themeable/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import Dropdown from '../Dropdown' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | export default themeable(Dropdown, (customizations, props) => ({ 5 | customize: { 6 | ...props.customize, 7 | borderColor: customizations.color_border, 8 | borderColorSelected: customizations.color_border_selected, 9 | borderRadius: customizations.radius_border, 10 | labelColor: customizations.color_text_secondary 11 | } 12 | })) 13 | -------------------------------------------------------------------------------- /components/themeable/Field.jsx: -------------------------------------------------------------------------------- 1 | import Field from '../Field' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | export default themeable(Field, (customizations, props) => ({ 5 | customize: { 6 | ...props.customize, 7 | borderColor: customizations.color_border, 8 | borderColorSelected: customizations.color_border_selected, 9 | borderRadius: customizations.radius_border, 10 | labelColor: customizations.color_text_secondary 11 | } 12 | })) 13 | -------------------------------------------------------------------------------- /components/themeable/Installments.jsx: -------------------------------------------------------------------------------- 1 | import Installments from '../Installments' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | export default themeable(Installments, (customizations, props) => ({ 5 | ...props, 6 | customize: { 7 | ...props.customize, 8 | borderColor: customizations.color_border, 9 | borderColorSelected: customizations.color_border_selected, 10 | borderRadius: customizations.radius_border, 11 | labelColor: customizations.color_text 12 | } 13 | })) 14 | -------------------------------------------------------------------------------- /components/themeable/Link.jsx: -------------------------------------------------------------------------------- 1 | import Link from '../Link' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | export default themeable(Link, (customizations, props) => ({ 5 | customize: { 6 | ...props.customize, 7 | textColor: customizations.color_link 8 | } 9 | })) 10 | -------------------------------------------------------------------------------- /components/themeable/Switch.jsx: -------------------------------------------------------------------------------- 1 | import Switch from '../Switch' 2 | import themeable from '../../lib/decorators/themeable' 3 | 4 | export default themeable(Switch, (customizations, props) => ({ 5 | ...props, 6 | customize: { 7 | ...props.customize, 8 | backgroundColor: customizations.color_checkbox, 9 | bulletColor: customizations.color_checkbox_checkmark 10 | } 11 | })) 12 | -------------------------------------------------------------------------------- /components/themeable/Text.jsx: -------------------------------------------------------------------------------- 1 | export { default as Paragraph } from './texts/Paragraph' 2 | export { default as PrimaryTitle } from './texts/PrimaryTitle' 3 | export { default as SecondaryTitle } from './texts/SecondaryTitle' 4 | export { default as Subtitle } from './texts/Subtitle' 5 | -------------------------------------------------------------------------------- /components/themeable/texts/Paragraph.jsx: -------------------------------------------------------------------------------- 1 | import Paragraph from '../../texts/Paragraph' 2 | import themeable from '../../../lib/decorators/themeable' 3 | 4 | const mapColor = ({ design }, { color_text, color_text_secondary }) => { 5 | switch (design) { 6 | case 'legal': return undefined 7 | case 'secondary': return color_text_secondary 8 | default: return color_text 9 | } 10 | } 11 | 12 | export default themeable(Paragraph, (customizations, props) => ({ 13 | ...props, 14 | style: { 15 | ...props.style, 16 | color: mapColor(props, customizations) 17 | } 18 | })) 19 | -------------------------------------------------------------------------------- /components/themeable/texts/PrimaryTitle.jsx: -------------------------------------------------------------------------------- 1 | import PrimaryTitle from '../../texts/PrimaryTitle' 2 | import themeable from '../../../lib/decorators/themeable' 3 | 4 | export default themeable(PrimaryTitle, (customizations, props) => ({ 5 | style: { 6 | ...props.style, 7 | color: customizations.color_header 8 | } 9 | })) 10 | -------------------------------------------------------------------------------- /components/themeable/texts/SecondaryTitle.jsx: -------------------------------------------------------------------------------- 1 | import SecondaryTitle from '../../texts/SecondaryTitle' 2 | import themeable from '../../../lib/decorators/themeable' 3 | 4 | export default themeable(SecondaryTitle, (customizations, props) => ({ 5 | style: { 6 | ...props.style, 7 | color: customizations.color_header 8 | } 9 | })) 10 | -------------------------------------------------------------------------------- /components/themeable/texts/Subtitle.jsx: -------------------------------------------------------------------------------- 1 | import Subtitle from '../../texts/Subtitle' 2 | import themeable from '../../../lib/decorators/themeable' 3 | 4 | export default themeable(Subtitle, (customizations, props) => ({ 5 | style: { 6 | ...props.style, 7 | color: customizations.color_header 8 | } 9 | })) 10 | -------------------------------------------------------------------------------- /components/uncontrolled/Field.jsx: -------------------------------------------------------------------------------- 1 | import compose from '../../lib/compose' 2 | import statefulValue from '../../lib/decorators/statefulValue' 3 | import statefulFocus from '../../lib/decorators/statefulFocus' 4 | import Field from '../Field' 5 | 6 | const UncontrolledField = compose(statefulFocus, statefulValue)(Field) 7 | 8 | UncontrolledField.displayName = 'UncontrolledField' 9 | 10 | export default UncontrolledField 11 | -------------------------------------------------------------------------------- /components/uncontrolled/Input.jsx: -------------------------------------------------------------------------------- 1 | import compose from '../../lib/compose' 2 | import statefulValue from '../../lib/decorators/statefulValue' 3 | import statefulFocus from '../../lib/decorators/statefulFocus' 4 | import Input from '../Input' 5 | 6 | const UncontrolledInput = compose(statefulFocus, statefulValue)(Input) 7 | 8 | UncontrolledInput.displayName = 'UncontrolledInput' 9 | 10 | export default UncontrolledInput 11 | -------------------------------------------------------------------------------- /components/uncontrolled/Installments.jsx: -------------------------------------------------------------------------------- 1 | import statefulValue from '../../lib/decorators/statefulValue' 2 | import Installments from '../Installments' 3 | 4 | const UncontrolledInstallments = statefulValue(Installments) 5 | 6 | UncontrolledInstallments.displayName = 'UncontrolledInstallments' 7 | 8 | export default UncontrolledInstallments 9 | -------------------------------------------------------------------------------- /components/uncontrolled/RadioGroup.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import RadioGroup from '../RadioGroup' 3 | 4 | export default class UncontrolledRadioGroup extends Component { 5 | 6 | constructor (props) { 7 | super(props) 8 | 9 | this.state = {selected: props.selected || (props.data && props.data[0].id)} 10 | 11 | this.onChange = this.onChange.bind(this) 12 | } 13 | 14 | onChange (selected) { 15 | this.setState({ selected }) 16 | this.props.onChange && this.props.onChange(selected) 17 | } 18 | 19 | render () { 20 | const { name, ...remainingProps } = this.props 21 | 22 | return ( 23 |
    24 | 27 | {name && } 28 |
    29 | ) 30 | } 31 | } 32 | 33 | UncontrolledRadioGroup.propTypes = { 34 | name: PropTypes.string, 35 | // Following PropTypes are the same as RadioGroup's 36 | // except selected and onChange are optional. 37 | selected: React.PropTypes.any, 38 | onChange: React.PropTypes.func, 39 | className: PropTypes.string, 40 | data: PropTypes.arrayOf(RadioGroup.optionSchema).isRequired 41 | } 42 | -------------------------------------------------------------------------------- /docs/patterns.md: -------------------------------------------------------------------------------- 1 | # Patterns & practices 2 | 3 | #### Avoid HTML's attribute names as props 4 | 5 | Using HTML's original attributes may make it hard or impossible to override them. E.g, prefer `design='primary'` over `type='primary'`. 6 | 7 | 8 | #### Write Controlled / uncontrolled components 9 | 10 | We follow React's controlled/uncontrolled pattern for most components (inputs, selectors...). This means that if the component has a value (`value`, `checked`, `selected`...), and this value is passed by it's father, it will be a controlled component, so some kind of change event (`onChange`, `onInput`, ...) must be implemented. Else, it's an uncontrolled component, and the initial value can be passed as `defaultValue`/`defaultChecked`. Ex: 11 | 12 | ``` 13 | // controlled 14 | 17 | this.setState({theoreticalInputValue: value}) 18 | } /> 19 | ``` 20 | 21 | ``` 22 | // uncontrolled 23 |
    24 | 26 | 27 | ``` 28 | 29 | #### Selector callback data should return full objects 30 | 31 | Every selector component (a component that contains a list and a selected value) should return the selected _object_ and the original event on the `onChange` callback. Ex: 32 | 33 | ``` 34 | { 36 | console.log( 37 | selectedItem.key, 38 | selectedItem.label, 39 | selectedItem.customInfo, 40 | event.target 41 | ) 42 | }) 43 | options={[ 44 | { key: 'foo', label: 'Foo', customInfo: 'bam' }, 45 | { key: 'bar', label: 'Bar' }, 46 | { key: 'baz', label: 'Baz' }, 47 | ]} /> 48 | ``` 49 | 50 | This way we avoid object searches (in case we returned only the key) and allow event manipulation if needed. -------------------------------------------------------------------------------- /example/Alerts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Code from './Code' 3 | import { Subtitle } from '../components/Text' 4 | import Alert, { Title, Paragraph } from '../components/Alert' 5 | 6 | export default function Alerts () { 7 | return ( 8 |
    9 | Error 10 | 11 | 12 | An error alert box heading 13 | Some text inside helps to get an idea of how the alert would look like. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 14 | Correlation ID: a4c531de-e35d-4901-93ae-44e32639b4b1 15 | 16 | 17 |
    18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /example/Blocks.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Code from './Code' 3 | import { Subtitle } from '../components/Text' 4 | import * as BlockInstallments from '../components/Block/Installments' 5 | 6 | export default function Blocks () { 7 | return ( 8 |
    9 | Installments 10 | 11 | 12 | Your Installments 13 | 14 | 18 | 23 | 24 | 25 | 26 |
    27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /example/Checklists.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Code from './Code' 3 | import Checklist from '../components/Checklist' 4 | import Theme from '../components/Theme' 5 | import ThemeableChecklist from '../components/themeable/Checklist' 6 | import { SecondaryTitle } from '../components/Text' 7 | 8 | export default function Checklists () { 9 | return ( 10 |
    11 | Regular 12 | 13 | 14 | 15 | Just one click and you're done 16 | Very little hassle 17 | Just do it! It can be done today, so why wait for tomorrow? 18 | 19 | 20 | 21 | Chromeless 22 | 23 | 24 | Just one click and you're done 25 | Very little hassle 26 | Just do it! It can be done today, so why wait for tomorrow? 27 | 28 | 29 | 30 | Themeable 31 | 32 | 33 | 34 | Just one click and you're done 35 | Very little hassle 36 | Just do it! It can be done today, so why wait for tomorrow? 37 | 38 | 39 | 40 |
    41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /example/Code.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SyntaxHighlighter from 'react-syntax-highlighter' 3 | import asString from 'react-to-jsx' 4 | 5 | export default function Code ({ children, width }) { 6 | const code = React.Children.map(children, (item) => { 7 | return asString(item, { indent: ' ' }) 8 | }).join('') 9 | 10 | return ( 11 |
    12 |
    13 | {children} 14 |
    15 | 16 | {code} 17 | 18 |
    19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /example/ContextMenus.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Code from './Code' 3 | import ContextMenu from '../components/ContextMenu' 4 | import Logout from '../components/icons/Logout' 5 | 6 | export default function ContextMenus () { 7 | return ( 8 |
    9 | 10 | 11 | Buttons 12 | Icons 13 | Labels 14 | 15 |
    Custom form
    16 |
    17 | 18 | 19 | 20 | Logout 21 | 22 |
    23 |
    24 | 25 | 26 |
    27 | 28 | Buttons 29 | Icons 30 | Labels 31 | 32 | 33 | 34 | Logout 35 | 36 | 37 |
    38 |
    39 |
    40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /example/Dialogs.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Button from '../components/Button' 3 | import Dialog from '../components/Dialog' 4 | import { CloseButton } from '../components/IconButton' 5 | import { PrimaryTitle, Subtitle, Paragraph } from '../components/Text' 6 | 7 | export default class Dialogs extends Component { 8 | constructor () { 9 | super() 10 | 11 | this.state = { 12 | dialog: { 13 | open: false 14 | } 15 | } 16 | } 17 | 18 | render () { 19 | return ( 20 |
    21 | 22 | Dialogs are full screen. Please click the button to show it. 23 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | this.setState({ dialog: { open: false } })} 38 | /> 39 | 40 | 41 | 42 | The title is primary 43 | Just trying to fill up space 44 | Completely synergize resource taxing relationships via premier niche markets. Professionally cultivate one-to-one customer service with robust ideas. Dynamically innovate resource-leveling customer service for state of the art customer service. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
    53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/Icons.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import AccountActivated from '../components/icons/AccountActivated' 4 | import AllSet from '../components/icons/AllSet' 5 | import Arrow from '../components/icons/Arrow' 6 | import Checkmark from '../components/icons/Checkmark' 7 | import Done from '../components/icons/Done' 8 | import Error from '../components/icons/Error' 9 | import Details from '../components/icons/Details' 10 | import Download from '../components/icons/Download' 11 | import ExtendDate from '../components/icons/ExtendDate' 12 | import Items from '../components/icons/Items' 13 | import Letter from '../components/icons/Letter' 14 | import Logout from '../components/icons/Logout' 15 | import Mail from '../components/icons/Mail' 16 | import NotFound from '../components/icons/NotFound' 17 | import OpenLetter from '../components/icons/OpenLetter' 18 | import PadLock from '../components/icons/PadLock' 19 | import Password from '../components/icons/Password' 20 | import Person from '../components/icons/Person' 21 | import Phone from '../components/icons/Phone' 22 | import Question from '../components/icons/Question' 23 | import Cancel from '../components/icons/Cancel' 24 | import Remind from '../components/icons/Remind' 25 | import Time from '../components/icons/Time' 26 | import SMS from '../components/icons/SMS' 27 | import Warning from '../components/icons/Warning' 28 | import Wrong from '../components/icons/Wrong' 29 | 30 | import { SecondaryTitle, Subtitle, Paragraph } from '../components/Text' 31 | import Code from './Code' 32 | import colors from '../components/icons/constants/colors' 33 | 34 | const icons = { 35 | big: [ 36 | AccountActivated, 37 | AllSet, 38 | Done, 39 | Error, 40 | Letter, 41 | NotFound, 42 | OpenLetter, 43 | PadLock, 44 | SMS, 45 | Time, 46 | Warning, 47 | Wrong 48 | ], 49 | 50 | tiny: [ 51 | Arrow, 52 | Checkmark, 53 | Details, 54 | Download, 55 | ExtendDate, 56 | Items, 57 | Logout, 58 | Mail, 59 | Password, 60 | Person, 61 | Phone, 62 | Question, 63 | Cancel, 64 | Remind 65 | ] 66 | } 67 | 68 | export default function Icons () { 69 | return ( 70 |
    71 | 72 | Each type of icon is designed for the size that it is displayed in. Resizing the icons is possible since they are SVG, but it's not recommended since they are drawn to have the line widths matching the line styles of the rest of the components. 73 | 74 | 75 | Colors 76 | Big 77 | 78 | {colors.map((name) => 79 | 87 | )} 88 | 89 | 90 | Tiny 91 | 92 | {colors.map((name) => 93 | 101 | )} 102 | 103 | 104 | Big icons 105 | 106 | {icons.big.map((Icon) => )} 107 | 108 | 109 | Tiny icons 110 | 111 | {icons.tiny.map((Icon) => )} 112 | 113 |
    114 | ) 115 | } 116 | -------------------------------------------------------------------------------- /example/Installments.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import InstallmentsComponent from '../components/Installments' 3 | import Theme from '../components/Theme' 4 | import ThemeableInstallmentsComponent from '../components/themeable/Installments' 5 | import UncontrolledInstallments from '../components/uncontrolled/Installments' 6 | import { SecondaryTitle, Subtitle } from '../components/Text' 7 | import Code from './Code' 8 | 9 | const options = [ 10 | { key: 'installments_6', value: '$64.17/mo.', connector: 'for', info: ' 6 months' }, 11 | { key: 'installments_12', value: '$32.09/mo.', connector: 'for', info: ' 12 months' }, 12 | { key: 'installments_24', value: '$16.05/mo.', connector: 'for', info: ' 24 months' } 13 | ] 14 | 15 | export default function Installments () { 16 | return ( 17 |
    18 | Regular 19 | 20 | console.log('You selected', key)} 22 | name='installments' 23 | value='installments_12' 24 | options={options} 25 | /> 26 | 27 | 28 | Uncontrolled 29 | 30 | console.log('You selected', key)} 32 | name='installments2' 33 | value='installments_24' 34 | options={options} 35 | /> 36 | 37 | 38 | Themeable 39 | 40 | 41 | console.log('You selected', key)} 43 | name='installments' 44 | value='installments_24' 45 | options={options} 46 | /> 47 | 48 | 49 |
    50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /example/Labels.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Label from '../components/Label' 3 | import Code from './Code' 4 | 5 | export default function Labels () { 6 | return ( 7 |
    8 | 9 | { 10 | Label.designs.map((design) => ( 11 | 14 | )) 15 | } 16 |
    17 | { 18 | Label.designs.map((design) => ( 19 | 22 | )) 23 | } 24 | 25 |
    26 | 29 | 32 | 33 |
    34 |
    35 |
    36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /example/Links.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from '../components/Link' 3 | import Code from './Code' 4 | import { SecondaryTitle } from '../components/Text' 5 | import Theme from '../components/Theme' 6 | import ThemeableLink from '../components/themeable/Link' 7 | 8 | export default function Links () { 9 | return ( 10 |
    11 | Regular 12 | 13 | Click me! 14 | 15 | 16 | Dynamic styling 17 | 18 | Click me! 19 | 20 | 21 | Themeable 22 | 23 | 24 |
    25 | Click me! 26 |
    27 |
    28 |
    29 |
    30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /example/Loaders.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Loader from '../components/Loader' 3 | import { SecondaryTitle, Subtitle } from '../components/Text' 4 | import Code from './Code' 5 | 6 | export default function Loaders () { 7 | return ( 8 |
    9 | Primary 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Inline 18 | 19 | 20 | 21 | 22 | 23 | Secondary 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | White 32 | 33 |
    34 | 35 | 36 | 37 | 38 |
    39 |
    40 |
    41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /example/Previews.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Preview, { PreviewTitle, PreviewLink } from '../components/Preview' 3 | import { SecondaryTitle } from '../components/Text' 4 | import Code from './Code' 5 | 6 | export default function Previews () { 7 | return ( 8 |
    9 | Default 10 | 11 | 12 | John Smith 13 | 1425 North Avenue Street
    14 | San Francisco
    15 | 94100 California
    16 | United States 17 |
    18 |
    19 | 20 | With link 21 | 22 | 23 | John Smith 24 | 1425 North Avenue Street
    25 | San Francisco
    26 | 94100 California
    27 | United States 28 | Change address 29 |
    30 |
    31 |
    32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /example/RadioGroups.jsx: -------------------------------------------------------------------------------- 1 | /* globals alert */ 2 | 3 | import React from 'react' 4 | import RadioGroup from '../components/RadioGroup' 5 | import UncontrolledRadioGroup from '../components/uncontrolled/RadioGroup' 6 | import Button from '../components/Button' 7 | import { SecondaryTitle, Subtitle, Paragraph } from '../components/Text' 8 | import Code from './Code' 9 | 10 | export default function RadioGroups () { 11 | const data = [ 12 | {id: 1, label: 'Lorem', description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'}, 13 | {id: 2, label: 'Ipsum', description: "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."} 14 | ] 15 | 16 | return ( 17 |
    18 | 19 | RadioGroups allow the user to pick one option among many 20 | with a design similar to HTML's input type radio. 21 | 22 | 23 | Stateless 24 | 25 | Stateless RadioGroups shall be used when you will control 26 | it's selected value. Therefore, you're in charge of 27 | implementing the props onChange and selected. 28 | 29 | 30 | As a rule of thumb, you'll use the stateless component if 31 | the RadioGroup is not in a form, and you want to trigger 32 | something on every change of this component. 33 | 34 | 35 | An example implementation of how you should consume the 36 | RadioGroup is the UncontrolledRadioGroup itself. 37 | 38 | 39 | 40 | 41 | 42 | Statefull 43 | 44 | Use the statefull selector if you don't want to control 45 | the selector, or you're using it on a form, passing the 46 | prop name. 47 | 48 | 49 | 50 | 51 | 52 | In a form 53 | 54 |
    { 55 | event.preventDefault() 56 | window.alert(event.target.lorem.value) 57 | }}> 58 | 59 | 60 | 61 |
    62 |
    63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /example/Selectors.jsx: -------------------------------------------------------------------------------- 1 | /* globals alert */ 2 | 3 | import React from 'react' 4 | import Button from '../components/Button' 5 | import RadioGroup from '../components/RadioGroup' 6 | import UncontrolledRadioGroup from '../components/uncontrolled/RadioGroup' 7 | import Selector from '../components/Selector' 8 | import { SecondaryTitle, Subtitle, Paragraph } from '../components/Text' 9 | import Code from './Code' 10 | 11 | export default function Selectors () { 12 | const data = [ 13 | {id: 1, label: 'Lorem', description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'}, 14 | {id: 2, label: 'Ipsum', description: "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."} 15 | ] 16 | 17 | return ( 18 |
    19 | RadioGroup 20 | 21 | RadioGroups allow the user to pick one option among many 22 | with a design similar to HTML's input type radio. 23 | 24 | 25 | Controlled 26 | 27 | Controlled RadioGroups shall be used when you will control 28 | it's selected value. Therefore, you're in charge of 29 | implementing the props onChange and selected. 30 | 31 | 32 | As a rule of thumb, you'll use the stateless component if 33 | the RadioGroup is not in a form, and you wanna trigger 34 | something on every change of this component. 35 | 36 | 37 | An example implementation of how you should consume the 38 | RadioGroup is the UncontrolledRadioGroup itself. 39 | 40 | 41 | 42 | 43 | 44 | Uncontrolled 45 | 46 | Use the stateful selector if you don't wanna control 47 | the selector, or you're using it on a form, passing the 48 | prop name. 49 | 50 | 51 | 52 | 53 | 54 | Uncontrolled in a form 55 | 56 |
    { 57 | event.preventDefault() 58 | window.alert(event.target.lorem.value) 59 | }}> 60 | 61 | 62 | 63 |
    64 | 65 | Selector 66 | 67 | Same as RadioGroups with different style. 68 | 69 | 70 | 71 | 72 |
    73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /example/Switches.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Switch from '../components/Switch' 3 | import Theme from '../components/Theme' 4 | import ThemeableSwitch from '../components/themeable/Switch' 5 | import Button from '../components/Button' 6 | import { SecondaryTitle } from '../components/Text' 7 | import Code from './Code' 8 | 9 | export default function Switches () { 10 | return ( 11 |
    12 | Regular 13 | 14 | This is a toggle switch 15 | 16 | 17 | Checked 18 | 19 | This is a toggle switch checked 20 | 21 | 22 | Error 23 | 24 | This is a toggle switch in an error state 25 | 26 | 27 | Right 28 | 29 | This is a toggle switch on the right side 30 | 31 | 32 | Disabled 33 | 34 | This is a toggle switch disabled 35 | 36 | 37 | Dynamic styling 38 | 39 | This is a toggle switch with dynamic styling 40 | 41 | 42 | Checkbox 43 | 44 | This is a toggle switch with checkbox design 45 | 46 | 47 | Checkbox with legal size text 48 | 49 | This is a toggle switch with checkbox design and legal size text that usually will fold into multiple lines 50 | 51 | 52 | Checkbox with dynamic styling 53 | 54 | This is a toggle switch with checkbox design and dynamic styling 55 | 56 | 57 | In a form 58 | 59 |
    { 60 | event.preventDefault() 61 | window.alert(event.target.fries.value) 62 | }}> 63 | Would you like fries? 64 | 65 |
    66 |
    67 | 68 | Outside a form (using a callback) 69 | 70 | { 71 | window.alert(value) 72 | }}>Would you like fries? 73 | 74 | 75 | Themeable 76 | 77 | 78 | Would you like fries? 79 | 80 | 81 |
    82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /example/examples.es6: -------------------------------------------------------------------------------- 1 | export Alerts from './Alerts' 2 | export Buttons from './Buttons' 3 | export Blocks from './Blocks' 4 | export Checklists from './Checklists' 5 | export ContextMenus from './ContextMenus' 6 | export Dialogs from './Dialogs' 7 | export Dropdowns from './Dropdowns' 8 | export Fields from './Fields' 9 | export Icons from './Icons' 10 | export Inputs from './Inputs' 11 | export Installments from './Installments' 12 | export Labels from './Labels' 13 | export Links from './Links' 14 | export Loaders from './Loaders' 15 | export Menus from './Menus' 16 | export Previews from './Previews' 17 | export RadioGroups from './RadioGroups' 18 | export Selectors from './Selectors' 19 | export Switches from './Switches' 20 | export Texts from './Texts' 21 | export Tooltips from './Tooltips' 22 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | {%=o.htmlWebpackPlugin.options.title || 'Webpack App'%} 14 | {% if (o.htmlWebpackPlugin.files.favicon) { %} 15 | 16 | {% } %} 17 | {% for (var css in o.htmlWebpackPlugin.files.css) { %} 18 | 19 | {% } %} 20 | 21 | 22 | 23 |
    24 | 25 | {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %} 26 | 27 | {% } %} 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/index.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import 'normalize.css' 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import classNames from 'classnames' 6 | import { PrimaryTitle } from '../components/Text' 7 | import Link from '../components/Link' 8 | import * as examples from './examples' 9 | import styles from './index.scss' 10 | 11 | function Root () { 12 | const anchor = window.location.hash.substring(1) 13 | const Example = anchor && examples[anchor] || Object.values(examples)[0] 14 | 15 | return ( 16 |
    17 | 31 |
    32 | {Example.name} 33 | 34 |
    35 |
    36 | ) 37 | } 38 | 39 | const render = () => ( 40 | ReactDOM.render( 41 | , 42 | document.getElementById('root') 43 | ) 44 | ) 45 | 46 | window.onhashchange = render 47 | render() 48 | -------------------------------------------------------------------------------- /example/index.scss: -------------------------------------------------------------------------------- 1 | @import url('//fonts.googleapis.com/css?family=Open+Sans:300,400,600'); 2 | 3 | body { 4 | font-family: 'Open Sans'; 5 | } 6 | 7 | main { 8 | display: table; 9 | } 10 | 11 | aside { 12 | -webkit-font-smoothing: antialiased; 13 | display: table-cell; 14 | font-size: 13px; 15 | font-weight: normal; 16 | padding: 10px 0 0 10px; 17 | width: 200px; 18 | } 19 | 20 | nav { 21 | position: fixed; 22 | 23 | a { 24 | display: block; 25 | padding: 5px; 26 | text-decoration: none; 27 | 28 | &.selected { 29 | text-decoration: underline; 30 | } 31 | } 32 | } 33 | 34 | .example { 35 | display: table-cell; 36 | max-width: 580px; 37 | width: 580px; 38 | } 39 | 40 | pre { 41 | background: #F5F5F7 !important; 42 | padding: 2em !important; 43 | margin: 0; 44 | } 45 | 46 | section { 47 | border-radius: 6px; 48 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.4); 49 | box-sizing: border-box; 50 | margin-bottom: 1em; 51 | overflow: hidden; 52 | } 53 | 54 | article { 55 | padding: 1em; 56 | } 57 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('./webpack.config.test.js') 2 | var argv = require('yargs').argv 3 | 4 | var browserToUse = ['PhantomJS'] 5 | if (process.env.BROWSER) { 6 | browserToUse = process.env.BROWSER.split(',') 7 | } 8 | 9 | module.exports = function (config) { 10 | config.set({ 11 | basePath: '', 12 | frameworks: ['mocha'], 13 | files: [ 14 | 'node_modules/babel-polyfill/dist/polyfill.js', 15 | 'tests/**/*.spec.*' 16 | ], 17 | exclude: [], 18 | preprocessors: { 19 | 'tests/**/*': ['webpack'] 20 | }, 21 | webpack: { 22 | devtool: 'inline-source-map', 23 | module: webpackConfig.module, 24 | resolve: webpackConfig.resolve, 25 | resolveLoader: webpackConfig.resolveLoader 26 | }, 27 | webpackMiddleware: { 28 | noInfo: true 29 | }, 30 | 31 | reporters: ['spec', 'saucelabs'], 32 | 33 | colors: true, 34 | logLevel: config.LOG_INFO, 35 | autoWatch: argv.watch, 36 | 37 | sauceLabs: { 38 | public: 'public', 39 | testName: 'klarna-react-components' 40 | }, 41 | 42 | captureTimeout: 120000, 43 | browserNoActivityTimeout: 1000000, 44 | 45 | customLaunchers: { 46 | SL_Chrome: { 47 | base: 'SauceLabs', 48 | browserName: 'chrome' 49 | }, 50 | SL_MAC_SAFARI_9_0: { 51 | base: 'SauceLabs', 52 | browserName: 'safari', 53 | platform: 'OS X 10.11', 54 | version: '9.0' 55 | }, 56 | SL_IE_10: { 57 | base: 'SauceLabs', 58 | browserName: 'internet explorer', 59 | platform: 'Windows 8', 60 | version: '10.0' 61 | }, 62 | SL_IE_11: { 63 | base: 'SauceLabs', 64 | browserName: 'internet explorer', 65 | platform: 'Windows 8.1', 66 | version: '11.0' 67 | }, 68 | SL_Android_4_2: { 69 | base: 'SauceLabs', 70 | browserName: 'android', 71 | platform: 'Linux', 72 | version: '4.2' 73 | }, 74 | SL_Android_4_3: { 75 | base: 'SauceLabs', 76 | browserName: 'android', 77 | platform: 'Linux', 78 | version: '4.3' 79 | }, 80 | SL_Android_4_4: { 81 | base: 'SauceLabs', 82 | browserName: 'android', 83 | platform: 'Linux', 84 | version: '4.4' 85 | }, 86 | SL_Android_5_0: { 87 | base: 'SauceLabs', 88 | browserName: 'android', 89 | platform: 'Linux', 90 | version: '5.0' 91 | }, 92 | SL_Android_5_1: { 93 | base: 'SauceLabs', 94 | browserName: 'android', 95 | platform: 'Linux', 96 | version: '5.1' 97 | }, 98 | SL_IOS_8_0: { 99 | base: 'SauceLabs', 100 | browserName: 'iPhone', 101 | platform: 'OS X 10.10', 102 | version: '8.0' 103 | }, 104 | SL_IOS_8_1: { 105 | base: 'SauceLabs', 106 | browserName: 'iPhone', 107 | platform: 'OS X 10.10', 108 | version: '8.1' 109 | }, 110 | SL_IOS_9_2: { 111 | base: 'SauceLabs', 112 | browserName: 'iPhone', 113 | platform: 'OS X 10.10', 114 | version: '9.2' 115 | } 116 | }, 117 | 118 | browsers: browserToUse, 119 | singleRun: !argv.watch, 120 | concurrency: Infinity 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /lib/combinations.es6: -------------------------------------------------------------------------------- 1 | export default (xs, ys) => 2 | xs 3 | .map((x) => ys.map((y) => [x, y])) 4 | .reduce((a, b) => a.concat(b), []) 5 | -------------------------------------------------------------------------------- /lib/compose.es6: -------------------------------------------------------------------------------- 1 | export default (...fs) => (x) => fs.reduceRight((acc, f) => f(acc), x) 2 | -------------------------------------------------------------------------------- /lib/decorators/statefulFocus.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default (Target) => { 4 | class DecoratedComponent extends Component { 5 | render () { 6 | const { 7 | onBlur, 8 | onFocus, 9 | ...props 10 | } = this.props 11 | 12 | return ( 13 | 19 | ) 20 | } 21 | 22 | handleBlur (onBlur, e) { 23 | this.setState({ 24 | focus: undefined 25 | }) 26 | 27 | onBlur && onBlur(e) 28 | } 29 | 30 | handleFocus (onFocus, e) { 31 | this.setState({ 32 | focus: true 33 | }) 34 | 35 | onFocus && onFocus(e) 36 | } 37 | } 38 | 39 | DecoratedComponent.displayName = Target.displayName || Target.name 40 | 41 | return DecoratedComponent 42 | } 43 | -------------------------------------------------------------------------------- /lib/decorators/statefulValue.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default (Target) => { 4 | class DecoratedComponent extends Component { 5 | render () { 6 | const { 7 | onChange, 8 | ...props 9 | } = this.props 10 | 11 | return ( 12 | 17 | ) 18 | } 19 | 20 | handleChange (onChange, e) { 21 | this.setState({ 22 | value: 23 | e && e.target 24 | ? e.target.value 25 | : e 26 | }) 27 | 28 | onChange && onChange(e) 29 | } 30 | } 31 | 32 | DecoratedComponent.displayName = Target.displayName || Target.name 33 | 34 | return DecoratedComponent 35 | } 36 | -------------------------------------------------------------------------------- /lib/decorators/themeable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withPropsFromContext } from 'react-context-props' 3 | 4 | const themeable = (Target, adapter) => { 5 | const ThemeableComponent = withPropsFromContext( 6 | ({ customizations, ...props }) => ( 7 | 13 | ), 14 | ['customizations'] 15 | ) 16 | 17 | ThemeableComponent.displayName = Target.displayName || Target.name 18 | 19 | return ThemeableComponent 20 | } 21 | 22 | export default themeable 23 | -------------------------------------------------------------------------------- /lib/features/fieldStates/index.es6: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react' 2 | 3 | export const getClassName = ({ disabled, error, warning }) => ({ 4 | 'is-disabled': disabled, 5 | 'is-error': error, 6 | 'is-warning': warning 7 | }) 8 | 9 | export const defaultProps = { 10 | disabled: false, 11 | error: false, 12 | warning: false 13 | } 14 | 15 | export const propTypes = { 16 | disabled: PropTypes.bool, 17 | error: PropTypes.bool, 18 | warning: PropTypes.bool 19 | } 20 | -------------------------------------------------------------------------------- /lib/features/keyboardEvents/index.es6: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react' 2 | 3 | const noop = () => {} 4 | 5 | const KEYS = { 6 | ENTER: 13, 7 | TAB: 9 8 | } 9 | 10 | export const handleKeyDown = ({ onTab, onEnter }) => (e) => { 11 | switch (e.keyCode) { 12 | case KEYS.TAB: { 13 | const direction = e.shiftKey ? -1 : 1 14 | return onTab(direction, e) 15 | } 16 | case KEYS.ENTER: { 17 | return onEnter(e) 18 | } 19 | } 20 | } 21 | 22 | handleKeyDown.defaultProps = { 23 | onTab: noop, 24 | onEnter: noop 25 | } 26 | 27 | handleKeyDown.propTypes = { 28 | onTab: PropTypes.func, 29 | onEnter: PropTypes.func 30 | } 31 | -------------------------------------------------------------------------------- /lib/features/programmaticFocus/index.es6: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react' 2 | import values from '../../values' 3 | 4 | export const FOCUS_TYPES = { 5 | FAKE: 'fake', 6 | REAL: 'real' 7 | } 8 | 9 | export const getClassName = ({ focus }) => focus && 'is-focused' 10 | 11 | export const maybeFocus = (document) => (type, input) => { 12 | switch (type) { 13 | case FOCUS_TYPES.REAL: 14 | if (document.activeElement !== input) { 15 | input.focus() 16 | } 17 | break 18 | case FOCUS_TYPES.FAKE: 19 | if (typeof input.scrollIntoViewIfNeeded === 'function') { 20 | input.scrollIntoViewIfNeeded() 21 | } 22 | break 23 | } 24 | } 25 | 26 | export const propTypes = { 27 | focus: PropTypes.oneOfType([ 28 | PropTypes.bool, 29 | PropTypes.oneOf(values(FOCUS_TYPES)) 30 | ]) 31 | } 32 | -------------------------------------------------------------------------------- /lib/features/stacking/index.es6: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react' 2 | import combinations from '../../../lib/combinations' 3 | import fieldSizeFraction from '../../../propTypes/fieldSizeFraction' 4 | 5 | export const position = { 6 | defaultProps: { 7 | bottom: false, 8 | center: false, 9 | left: false, 10 | right: false, 11 | top: false 12 | }, 13 | 14 | getClassName: (props) => 15 | ( 16 | position.positionCombinations 17 | .concat(position.positionList.map((x) => [x])) 18 | .find((combination) => 19 | combination.length === 1 20 | ? props[combination[0]] 21 | : (props[combination[0]] && props[combination[1]]) 22 | ) || [] 23 | ).join('-') 24 | , 25 | 26 | getBorderRadii: ({ top, bottom, square }, radius) => 27 | ( 28 | top 29 | ? { 30 | borderBottomLeftRadius: '0px', 31 | borderBottomRightRadius: '0px', 32 | borderTopLeftRadius: radius, 33 | borderTopRightRadius: radius 34 | } 35 | : bottom 36 | ? { 37 | borderBottomLeftRadius: radius, 38 | borderBottomRightRadius: radius, 39 | borderTopLeftRadius: '0px', 40 | borderTopRightRadius: '0px' 41 | } 42 | : square 43 | ? { 44 | borderRadius: '0px' 45 | } 46 | : { 47 | borderRadius: radius 48 | } 49 | ) 50 | , 51 | 52 | positionCombinations: combinations( 53 | ['bottom', 'top'], 54 | ['left', 'right'] 55 | ), 56 | 57 | positionList: [ 58 | 'bottom', 59 | 'center', 60 | 'left', 61 | 'right', 62 | 'top' 63 | ], 64 | 65 | propTypes: { 66 | bottom: PropTypes.bool, 67 | center: PropTypes.bool, 68 | left: PropTypes.bool, 69 | right: PropTypes.bool, 70 | top: PropTypes.bool 71 | } 72 | } 73 | 74 | const MAX_SIZE = 5 75 | 76 | export const size = { 77 | defaultProps: { 78 | size: '1/1' 79 | }, 80 | 81 | getClassName: (props) => size.map[props.size], 82 | 83 | map: { 84 | '1/2': 'half', 85 | '1/3': 'third', 86 | '2/3': 'two-thirds', 87 | '1/4': 'quarter', 88 | '2/4': 'half', 89 | '3/4': 'three-quarters', 90 | '1/5': 'twenty', 91 | '2/5': 'forty', 92 | '3/5': 'sixty', 93 | '4/5': 'eighty' 94 | }, 95 | 96 | max: MAX_SIZE, 97 | 98 | propTypes: { 99 | size: fieldSizeFraction(MAX_SIZE) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/toObjectWithValue.es6: -------------------------------------------------------------------------------- 1 | export default (value) => (list) => 2 | list.reduce((accumulator, item) => ({ 3 | ...accumulator, 4 | [item]: value 5 | }), {}) 6 | -------------------------------------------------------------------------------- /lib/validators.es6: -------------------------------------------------------------------------------- 1 | export const composeValidators = (...validators) => (...values) => 2 | validators.reduceRight( 3 | (accumulator, validator) => accumulator || validator(...values), 4 | null 5 | ) 6 | 7 | const any = (...conditions) => conditions.reduce((a, b) => a || b) 8 | 9 | export const isFraction = (props, propName, componentName) => { 10 | const values = props[propName].toString().split('/') 11 | 12 | return any( 13 | values.length !== 2, 14 | isNaN(values[0]), 15 | isNaN(values[1]) 16 | ) 17 | ? new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`: needs to be a fraction, such as \`1/3\``) 18 | : undefined 19 | } 20 | 21 | 22 | export const isPositiveIntegerFraction = (props, propName, componentName) => { 23 | const values = props[propName].split('/') 24 | const notAPositiveInteger = (value) => /[^0-9]/.test(value) 25 | 26 | return any( 27 | notAPositiveInteger(values[0]), 28 | notAPositiveInteger(values[1]), 29 | parseInt(values[0], 10) === 0, 30 | parseInt(values[1], 10) === 0 31 | ) 32 | ? new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`: fraction values need to be positive integers`) 33 | : undefined 34 | } 35 | 36 | export const isDenominatorBelowThreshold = (threshold) => (props, propName, componentName) => { 37 | const denominator = parseInt(props[propName].split('/')[1], 10) 38 | 39 | return denominator > threshold 40 | ? new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`: values needs to be lower or equal to \`${threshold}\``) 41 | : undefined 42 | } 43 | 44 | export const isNumeratorAboveDenominator = (props, propName, componentName) => { 45 | const [numerator, denominator] = props[propName].split('/').map((value) => parseInt(value, 10)) 46 | 47 | return numerator > denominator 48 | ? new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`: numerator needs to be lower or equal to the denominator (\`${denominator}\` in this case)`) 49 | : undefined 50 | } 51 | -------------------------------------------------------------------------------- /lib/values.es6: -------------------------------------------------------------------------------- 1 | export default (x) => Object.keys(x).map((k) => x[k]) 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@klarna/ui-react-components", 3 | "version": "0.16.0", 4 | "description": "Klarna's UI React Components", 5 | "repository": "https://github.com/klarna/ui-react-components", 6 | "license": "SEE LICENSE IN LICENSE", 7 | "scripts": { 8 | "start": "webpack-dev-server --port 7777 --host 0.0.0.0 --history-api-fallback", 9 | "build": "webpack", 10 | "test": "standard && karma start karma.conf.js", 11 | "lint": "standard", 12 | "test:watch": "npm test -- --watch" 13 | }, 14 | "author": "Klarna front end people", 15 | "dependencies": { 16 | "@klarna/ui-css-components": "9.3.0", 17 | "classnames": "2.2.5", 18 | "parse-color": "1.0.0", 19 | "react-context-props": "^0.1.3" 20 | }, 21 | "peerDependencies": { 22 | "react": "^15.0.2" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer-loader": "^3.1.0", 26 | "babel-core": "^6.7.4", 27 | "babel-loader": "^6.2.4", 28 | "babel-polyfill": "^6.0.16", 29 | "babel-preset-es2015": "^6.1.2", 30 | "babel-preset-react": "^6.1.2", 31 | "babel-preset-stage-0": "^6.1.2", 32 | "css-loader": "^0.19.0", 33 | "file-loader": "^0.8.5", 34 | "html-webpack-plugin": "^1.6.1", 35 | "karma": "^0.13.19", 36 | "karma-chrome-launcher": "0.2.2", 37 | "karma-coverage": "0.5.2", 38 | "karma-firefox-launcher": "0.1.7", 39 | "karma-ie-launcher": "0.2.0", 40 | "karma-mocha": "^0.2.1", 41 | "karma-phantomjs-launcher": "^0.2.3", 42 | "karma-prettybrowser-reporter": "git+https://github.com/cpapazaf/karma-prettybrowser-reporter.git", 43 | "karma-safari-launcher": "0.1.1", 44 | "karma-sauce-launcher": "^0.3.1", 45 | "karma-spec-reporter": "0.0.23", 46 | "karma-webdriver-launcher": "1.0.4", 47 | "karma-webpack": "^1.7.0", 48 | "mocha": "^2.3.4", 49 | "node-sass": "^3.4.2", 50 | "normalize.css": "^4.1.1", 51 | "null-loader": "^0.1.1", 52 | "phantomjs": "^1.9.19", 53 | "ramda": "^0.21.0", 54 | "react": "^15.0.2", 55 | "react-addons-test-utils": "^15.0.2", 56 | "react-dom": "^15.0.2", 57 | "react-motion": "^0.4.2", 58 | "react-syntax-highlighter": "^1.1.2", 59 | "react-to-jsx": "^1.3.2", 60 | "resolve-url-loader": "^1.2.0", 61 | "sass-loader": "^3.2.0", 62 | "sinon": "git+https://github.com/sinonjs/sinon.git", 63 | "standard": "^6.0.8", 64 | "style-loader": "^0.12.4", 65 | "url-loader": "^0.5.6", 66 | "webpack": "^1.12.2", 67 | "webpack-dev-server": "^1.12.1", 68 | "webpack-error-notification": "^0.1.6", 69 | "yargs": "^3.31.0" 70 | }, 71 | "standard": { 72 | "globals": [ 73 | "describe", 74 | "it" 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /propTypes/fieldSizeFraction.es6: -------------------------------------------------------------------------------- 1 | import { 2 | composeValidators, 3 | isDenominatorBelowThreshold, 4 | isFraction, 5 | isNumeratorAboveDenominator, 6 | isPositiveIntegerFraction 7 | } from '../lib/validators' 8 | 9 | export default (threshold) => composeValidators( 10 | isNumeratorAboveDenominator, 11 | isDenominatorBelowThreshold(threshold), 12 | isPositiveIntegerFraction, 13 | isFraction 14 | ) 15 | -------------------------------------------------------------------------------- /propTypes/validateSize.es6: -------------------------------------------------------------------------------- 1 | export default function validateSize (props, prop, component) { 2 | if (isSize(props[prop])) { 3 | return null 4 | } 5 | 6 | return new Error(`${prop} '${props[prop]}' in ${component} is not valid.`) 7 | } 8 | 9 | const isSize = (size) => ( 10 | typeof size === 'undefined' || 11 | typeof size === 'string' && 12 | size.match(/^(0|\d*\.?\d+?(px|%|em|rem|ex|cm|mm|in|pt|pc|ch|vh|vw|vmin|vmax))$/) 13 | ) 14 | -------------------------------------------------------------------------------- /tests/Alert.spec.jsx: -------------------------------------------------------------------------------- 1 | import Alert, { Title, Paragraph } from '../components/Alert' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Alert) 6 | const renderTitle = renderer(Title) 7 | const renderParagraph = renderer(Paragraph) 8 | 9 | describe('Alert', () => { 10 | describe('error', () => { 11 | const alert = render({design: 'error'}, [ 12 | renderTitle({}, 'Header'), 13 | renderParagraph({}, 'LoremIpsum') 14 | ]) 15 | const title = alert.props.children[0] 16 | const paragraph = alert.props.children[1] 17 | 18 | it("renders tag 'div'", () => { 19 | equal(alert.type, 'div') 20 | }) 21 | 22 | it("has className 'cui__alert--error'", () => { 23 | ok(alert.props.className.match('cui__alert--error')) 24 | }) 25 | 26 | it('should have the content', () => { 27 | equal(alert.props.children.length, 2) 28 | ok(title.props.children.match('Header')) 29 | ok(paragraph.props.children.match('LoremIpsum')) 30 | }) 31 | }) 32 | 33 | describe('Title', () => { 34 | const alert = render({design: 'error'}, renderTitle({}, 'Header')) 35 | const title = alert.props.children 36 | 37 | it("renders tag 'h1'", () => { 38 | equal(title.type, 'h1') 39 | }) 40 | 41 | it("has className 'cui__alert__title'", () => { 42 | ok(title.props.className.match('cui__alert__title')) 43 | }) 44 | 45 | it('should have the content', () => { 46 | ok(title.props.children.match('Header')) 47 | }) 48 | }) 49 | 50 | describe('Paragraph', () => { 51 | const alert = render({design: 'error'}, renderParagraph({}, 'LoremIpsum')) 52 | const paragraph = alert.props.children 53 | 54 | it("renders tag 'p'", () => { 55 | equal(paragraph.type, 'p') 56 | }) 57 | 58 | it("has className 'cui__alert__paragraph'", () => { 59 | ok(paragraph.props.className.match('cui__alert__paragraph')) 60 | }) 61 | 62 | it('should have the content', () => { 63 | ok(paragraph.props.children.match('LoremIpsum')) 64 | }) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /tests/Amount.spec.jsx: -------------------------------------------------------------------------------- 1 | import Amount from '../components/texts/Amount' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import describePalette from './describePalette' 5 | 6 | const render = renderer(Amount) 7 | 8 | describe('Amount', () => { 9 | describe('default', () => { 10 | const primaryTitle = render({}, 'LoremIpsum') 11 | 12 | it('renders tag "p"', () => { 13 | equal(primaryTitle.type, 'p') 14 | }) 15 | 16 | it('has className "cui__amount-text"', () => { 17 | ok(primaryTitle.props.className.match('cui__amount-text')) 18 | }) 19 | 20 | it('should have the content', () => { 21 | ok(primaryTitle.props.children.match('LoremIpsum')) 22 | }) 23 | }) 24 | 25 | describePalette(render) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/Checklist.spec.jsx: -------------------------------------------------------------------------------- 1 | import Checklist from '../components/Checklist' 2 | import { equal, ok } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const renderChecklist = renderer(Checklist) 6 | const renderChecklistItem = renderer(Checklist.Item) 7 | 8 | const items = [ 9 | 'Just one click and you\'re done', 10 | 'Very little hassle', 11 | 'Just do it! It can be done today, so why wait for tomorrow?' 12 | ] 13 | 14 | describe('Checklist', () => { 15 | describe('default', () => { 16 | const checklist = renderChecklist( 17 | {}, 18 | items.map((item, index) => 19 | renderChecklistItem({ key: index }, item)) 20 | ) 21 | 22 | it('is "ul"', () => { 23 | equal(checklist.type, 'ul') 24 | }) 25 | 26 | it('has class ".cui__checklist"', () => { 27 | equal(checklist.props.className, 'cui__checklist') 28 | }) 29 | 30 | items.forEach((item, index) => { 31 | describe(`item #${index}`, () => { 32 | const renderedItem = checklist.props.children[index] 33 | 34 | it('is tag "li"', () => { 35 | equal(renderedItem.type, 'li') 36 | }) 37 | 38 | it('has class ".cui__checklist__item"', () => { 39 | equal(renderedItem.props.className, 'cui__checklist__item') 40 | }) 41 | 42 | it('has svg icon', () => { 43 | equal(renderedItem.props.children[0].type, 'svg') 44 | }) 45 | 46 | it('icon has class "cui__checklist__checkmark"', () => { 47 | equal(renderedItem.props.children[0].props.className, 'cui__checklist__checkmark') 48 | }) 49 | 50 | it('has content of the first item', () => { 51 | equal(renderedItem.props.children[1], items[index]) 52 | }) 53 | }) 54 | }) 55 | }) 56 | 57 | describe('chromeless', () => { 58 | const checklist = renderChecklist({ chromeless: true }, []) 59 | 60 | it('has class chromeless', () => { 61 | ok(checklist.props.className.match('chromeless')) 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /tests/Dialog.spec.jsx: -------------------------------------------------------------------------------- 1 | import Dialog from '../components/Dialog' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Dialog) 6 | const renderIcon = renderer(Dialog.Icon) 7 | const renderContent = renderer(Dialog.Content) 8 | const renderFooter = renderer(Dialog.Footer) 9 | const renderOverlay = renderer(Dialog.Overlay) 10 | 11 | describe('Dialog', () => { 12 | describe('default', () => { 13 | const dialog = render({}) 14 | 15 | it("renders tag 'div'", () => { 16 | equal(dialog.type, 'div') 17 | }) 18 | 19 | it("has className 'cui__dialog'", () => { 20 | equal(dialog.props.className, 'cui__dialog') 21 | }) 22 | }) 23 | 24 | describe('icon', () => { 25 | const dialog = render({}, renderIcon({}, 'X')) 26 | const dialogIcon = dialog.props.children 27 | 28 | it("renders tag 'div'", () => { 29 | equal(dialogIcon.type, 'div') 30 | }) 31 | 32 | it("has className 'cui__dialog__icon'", () => { 33 | equal(dialogIcon.props.className, 'cui__dialog__icon') 34 | }) 35 | 36 | it('has the content', () => { 37 | ok(dialogIcon.props.children.match('X')) 38 | }) 39 | }) 40 | 41 | describe('content', () => { 42 | const dialog = render({}, renderContent({}, 'Some text')) 43 | const dialogContent = dialog.props.children 44 | const dialogContentInner = dialogContent.props.children 45 | 46 | it("renders tag 'div'", () => { 47 | equal(dialogContent.type, 'div') 48 | }) 49 | 50 | it("has className 'cui__dialog__content'", () => { 51 | equal(dialogContent.props.className, 'cui__dialog__content') 52 | }) 53 | 54 | describe('inner', () => { 55 | it("renders tag 'div'", () => { 56 | equal(dialogContentInner.type, 'div') 57 | }) 58 | 59 | it("has className 'cui__dialog__content--inner'", () => { 60 | equal(dialogContentInner.props.className, 'cui__dialog__content--inner') 61 | }) 62 | 63 | it('has the content', () => { 64 | ok(dialogContentInner.props.children.match('Some text')) 65 | }) 66 | }) 67 | }) 68 | 69 | describe('footer', () => { 70 | const dialog = render({}, renderFooter({}, 'Some text')) 71 | const dialogFooter = dialog.props.children 72 | const dialogFooterInner = dialogFooter.props.children 73 | 74 | it("renders tag 'div'", () => { 75 | equal(dialogFooter.type, 'div') 76 | }) 77 | 78 | it("has className 'cui__dialog__footer'", () => { 79 | equal(dialogFooter.props.className, 'cui__dialog__footer') 80 | }) 81 | 82 | describe('inner', () => { 83 | it("renders tag 'div'", () => { 84 | equal(dialogFooterInner.type, 'div') 85 | }) 86 | 87 | it("has className 'cui__dialog__footer--inner'", () => { 88 | equal(dialogFooterInner.props.className, 'cui__dialog__footer--inner') 89 | }) 90 | 91 | it('has the content', () => { 92 | ok(dialogFooterInner.props.children.match('Some text')) 93 | }) 94 | }) 95 | }) 96 | 97 | describe('overlay', () => { 98 | const dialogOverlay = renderOverlay({}, 'Some text') 99 | const dialogTable = dialogOverlay.props.children 100 | const dialogCell = dialogTable.props.children 101 | 102 | it("renders tag 'div'", () => { 103 | equal(dialogOverlay.type, 'div') 104 | }) 105 | 106 | it("has className 'cui__dialog__overlay'", () => { 107 | equal(dialogOverlay.props.className, 'cui__dialog__overlay') 108 | }) 109 | 110 | describe('table', () => { 111 | it("renders tag 'div'", () => { 112 | equal(dialogTable.type, 'div') 113 | }) 114 | 115 | it("has className 'cui__dialog__table'", () => { 116 | equal(dialogTable.props.className, 'cui__dialog__table') 117 | }) 118 | }) 119 | 120 | describe('cell', () => { 121 | it("renders tag 'div'", () => { 122 | equal(dialogCell.type, 'div') 123 | }) 124 | 125 | it("has className 'cui__dialog__cell'", () => { 126 | equal(dialogCell.props.className, 'cui__dialog__cell') 127 | }) 128 | 129 | it('has the content', () => { 130 | ok(dialogCell.props.children.match('Some text')) 131 | }) 132 | }) 133 | 134 | describe('show', () => { 135 | const dialogOverlay = renderOverlay({ show: true }) 136 | 137 | it('has class "is-visible"', () => { 138 | ok(dialogOverlay.props.className.match('is-visible')) 139 | }) 140 | }) 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /tests/Fieldset.spec.jsx: -------------------------------------------------------------------------------- 1 | import Fieldset from '../components/Fieldset' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Fieldset) 6 | 7 | describe('Fieldset', () => { 8 | describe('default', () => { 9 | const fieldset = render({}, 'LoremIpsum') 10 | 11 | it("renders tag 'div'", () => { 12 | equal(fieldset.type, 'div') 13 | }) 14 | 15 | it("has className 'cui__fieldset'", () => { 16 | equal(fieldset.props.className, 'cui__fieldset') 17 | }) 18 | 19 | it('should have the content', () => { 20 | ok(fieldset.props.children.match('LoremIpsum')) 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/Input.spec.jsx: -------------------------------------------------------------------------------- 1 | import Input from '../components/Input' 2 | import assert, { equal } from 'assert' 3 | import { renderer, shallow } from './helpers' 4 | 5 | const render = renderer(Input) 6 | 7 | const label = (input) => input.props.children[1] 8 | const _input = (input) => input.props.children[2] 9 | 10 | describe('Input', () => { 11 | describe('default', () => { 12 | const input = render({ name: 'test', label: 'Test' }) 13 | 14 | it('renders cui__input', () => { 15 | equal(input.type, 'div') 16 | equal(input.props.className, 'cui__input') 17 | }) 18 | 19 | it('renders a label', () => { 20 | equal(label(input).type, 'label') 21 | equal(label(input).props.children, 'Test') 22 | equal(label(input).props.className, 'cui__input__label') 23 | }) 24 | 25 | it('renders an enabled text input', () => { 26 | equal(_input(input).type, 'input') 27 | equal(_input(input).props.value, '') 28 | equal(_input(input).props.name, 'test') 29 | equal(_input(input).props.className, 'cui__input__input') 30 | assert(_input(input).props.disabled === false) 31 | }) 32 | }) 33 | 34 | describe('sizes', () => { 35 | it("when 'big' has className 'big'", () => { 36 | const input = render({ big: true, name: 'test', label: 'Test' }) 37 | 38 | equal(input.props.className, 'cui__input big') 39 | }) 40 | 41 | it("when 'giant' has className 'giant'", () => { 42 | const input = render({ giant: true, name: 'test', label: 'Test' }) 43 | 44 | equal(input.props.className, 'cui__input giant') 45 | }) 46 | }) 47 | 48 | describe('filled', () => { 49 | it("when has initial value has className 'is-filled'", () => { 50 | const input = render({ value: 'something', name: 'filled', label: 'Filled' }) 51 | 52 | equal(input.props.className, 'cui__input is-filled') 53 | }) 54 | }) 55 | 56 | describe('focused', () => { 57 | it("has className 'is-focused'", () => { 58 | const renderer = shallow(Input, { name: 'focused', label: 'Focused', focus: true }) 59 | 60 | equal(renderer.getRenderOutput().props.className, 'cui__input is-focused') 61 | }) 62 | }) 63 | 64 | describe('error', () => { 65 | const input = render({ error: true, name: 'filled', label: 'Ooops' }) 66 | 67 | it("has className 'is-error'", () => { 68 | equal(input.props.className, 'cui__input is-error') 69 | }) 70 | 71 | it('error is in the label', () => { 72 | equal(label(input).props.children, 'Ooops') 73 | }) 74 | }) 75 | 76 | describe('warning', () => { 77 | const input = render({ warning: true, name: 'filled', label: 'Hey!' }) 78 | 79 | it("has className 'is-warning'", () => { 80 | equal(input.props.className, 'cui__input is-warning') 81 | }) 82 | 83 | it('warning is in the label', () => { 84 | equal(label(input).props.children, 'Hey!') 85 | }) 86 | }) 87 | 88 | describe('disabled', () => { 89 | const input = render({ disabled: true, name: 'filled', label: 'Filled' }) 90 | 91 | it("has className 'is-disabled'", () => { 92 | equal(input.props.className, 'cui__input is-disabled') 93 | }) 94 | 95 | it('input is disabled', () => { 96 | assert(_input(input).props.disabled === true) 97 | }) 98 | }) 99 | }) 100 | -------------------------------------------------------------------------------- /tests/Label.spec.jsx: -------------------------------------------------------------------------------- 1 | import Label from '../components/Label' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Label) 6 | 7 | describe('Label', () => { 8 | describe('default', () => { 9 | const label = render({}, 'LoremIpsum') 10 | 11 | it("renders tag 'span'", () => { 12 | equal(label.type, 'span') 13 | }) 14 | 15 | it("has className 'cui__label'", () => { 16 | equal(label.props.className, 'cui__label') 17 | }) 18 | 19 | it('should have the content', () => { 20 | ok(label.props.children.match('LoremIpsum')) 21 | }) 22 | }) 23 | 24 | describe('designs', () => { 25 | Label.designs.map((design) => { 26 | const label = render({ design }, 'LoremIpsum') 27 | 28 | it(`has className "${design}" when design is set`, () => { 29 | ok(label.props.className.match(design)) 30 | }) 31 | }) 32 | }) 33 | 34 | describe('outline', () => { 35 | const label = render({ outline: true }, 'LoremIpsum') 36 | 37 | it('has className "outline"', () => { 38 | ok(label.props.className.match('outline')) 39 | }) 40 | }) 41 | 42 | describe('inverted', () => { 43 | const label = render({ inverted: true }, 'LoremIpsum') 44 | 45 | it('has className "inverted"', () => { 46 | ok(label.props.className.match('inverted')) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/Link.spec.jsx: -------------------------------------------------------------------------------- 1 | import Link from '../components/Link' 2 | import { equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Link) 6 | 7 | describe('Link', () => { 8 | describe('default', () => { 9 | const link = render() 10 | 11 | it('renders tag "a"', () => { 12 | equal('a', link.type) 13 | }) 14 | 15 | it('has className "cui__link"', () => { 16 | equal('cui__link', link.props.className) 17 | }) 18 | }) 19 | 20 | it('allows passing custom "className" without overriding defaults', () => { 21 | const link = render({ className: 'custom' }) 22 | 23 | equal('cui__link custom', link.props.className) 24 | }) 25 | 26 | describe('customize', () => { 27 | const link = render({ customize: { textColor: 'green' } }) 28 | 29 | it('has className "dynamic-styling"', () => { 30 | equal('cui__link dynamic-styling', link.props.className) 31 | }) 32 | 33 | it('has the correct customized styles', () => { 34 | equal(link.props.style.color, 'green') 35 | }) 36 | }) 37 | 38 | it('allows more props', () => { 39 | const link = render({ href: 'http://test.com', onClick: () => {} }) 40 | 41 | equal('http://test.com', link.props.href) 42 | equal('function', typeof link.props.onClick) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/Loader.spec.jsx: -------------------------------------------------------------------------------- 1 | import Loader from '../components/Loader' 2 | import { equal, ok } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Loader) 6 | 7 | describe('Loader', () => { 8 | describe('default', () => { 9 | const loader = render() 10 | 11 | it('renders tag "svg"', () => { 12 | equal('svg', loader.type) 13 | }) 14 | 15 | it('has className "loader"', () => { 16 | equal('loader', loader.props.className) 17 | }) 18 | }) 19 | 20 | it("allows passing custom 'className' without overriding defaults", () => { 21 | const loader = render({ className: 'custom' }) 22 | 23 | equal('loader custom', loader.props.className) 24 | }) 25 | 26 | describe('inline', () => { 27 | const loader = render({ inline: true }) 28 | 29 | it('has class "inline"', () => { 30 | ok(loader.props.className.match('inline')) 31 | }) 32 | }) 33 | 34 | describe('size', () => { 35 | it('has width 30 when size is "big"', () => { 36 | equal(30, render({ size: 'big' }).props.width) 37 | }) 38 | 39 | it('has width 20 when size is default', () => { 40 | equal(20, render().props.width) 41 | }) 42 | 43 | it('has width 15 when size is "small"', () => { 44 | equal(15, render({ size: 'small' }).props.width) 45 | }) 46 | 47 | it('has width 10 when size is "tiny"', () => { 48 | equal(10, render({ size: 'tiny' }).props.width) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/Paragraph.spec.jsx: -------------------------------------------------------------------------------- 1 | import Paragraph from '../components/texts/Paragraph' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import describePalette from './describePalette' 5 | 6 | const render = renderer(Paragraph) 7 | 8 | describe('Paragraph', () => { 9 | describe('default', () => { 10 | const paragraph = render({}, 'LoremIpsum') 11 | 12 | it("renders tag 'p'", () => { 13 | equal(paragraph.type, 'p') 14 | }) 15 | 16 | it("has className 'cui__paragraph--primary'", () => { 17 | ok(paragraph.props.className.match('cui__paragraph--primary')) 18 | }) 19 | 20 | it('should have the content', () => { 21 | ok(paragraph.props.children.match('LoremIpsum')) 22 | }) 23 | }) 24 | 25 | describePalette(render) 26 | 27 | describe('condensed', () => { 28 | const paragraph = render({condensed: true}, 'LoremIpsum') 29 | 30 | it('does have class condensed', () => { 31 | ok(paragraph.props.className.match('condensed')) 32 | }) 33 | }) 34 | 35 | describe('margins', () => { 36 | const paragraph = render({margins: true}, 'LoremIpsum') 37 | 38 | it("has class 'default-margins'", () => { 39 | ok(paragraph.props.className.match('default-margins')) 40 | }) 41 | }) 42 | 43 | describe('design:secondary', () => { 44 | const paragraph = render({design: 'secondary'}, 'LoremIpsum') 45 | 46 | it("has className 'cui__paragraph--secondary'", () => { 47 | ok(paragraph.props.className.match('cui__paragraph--secondary')) 48 | }) 49 | }) 50 | 51 | describe('design:legal', () => { 52 | const paragraph = render({design: 'legal'}, 'LoremIpsum') 53 | 54 | it("has className 'cui__paragraph--legal'", () => { 55 | ok(paragraph.props.className.match('cui__paragraph--legal')) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /tests/PayButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import PayButton from '../components/PayButton' 2 | import Button from '../components/Button' 3 | import { ok, equal } from 'assert' 4 | import { renderer } from './helpers' 5 | 6 | const render = renderer(PayButton) 7 | 8 | describe('PayButton', () => { 9 | describe('default', () => { 10 | const payButton = render({ price: '10.15' }, 'Click me') 11 | 12 | it('renders element Button', () => { 13 | equal(payButton.type, Button) 14 | }) 15 | 16 | it("has class 'has-price'", () => { 17 | equal(payButton.props.className, 'has-price') 18 | }) 19 | 20 | describe('loading', () => { 21 | const payButton = render({ price: '10.15', loading: true }, 'Click me') 22 | 23 | it("does not have class 'has-price'", () => { 24 | ok(!payButton.props.className.match('has-price')) 25 | }) 26 | }) 27 | 28 | describe('price', () => { 29 | it('has the span component inside the button', () => { 30 | equal(payButton.props.children[1].type, 'span') 31 | }) 32 | 33 | it('has the cui__button__price component inside the button', () => { 34 | equal(payButton.props.children[1].props.className, 'cui__button__price') 35 | }) 36 | 37 | it('has the price as the content of the span', () => { 38 | ok(payButton.props.children[1].props.children.match('10.15')) 39 | }) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/Preview.spec.jsx: -------------------------------------------------------------------------------- 1 | import Preview, { PreviewTitle, PreviewLink } from '../components/Preview' 2 | import { equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | describe('Preview', () => { 6 | const render = renderer(Preview) 7 | 8 | describe('default', () => { 9 | const preview = render() 10 | 11 | it("renders tag 'div'", () => { 12 | equal('div', preview.type) 13 | }) 14 | 15 | it("has className 'cui__preview'", () => { 16 | equal('cui__preview', preview.props.className) 17 | }) 18 | 19 | it("contains a div with className 'cui__preview__content'", () => { 20 | equal('cui__preview__content', preview.props.children.props.className) 21 | }) 22 | }) 23 | 24 | it("allows passing custom 'className' without overriding defaults", () => { 25 | const preview = render({ className: 'custom' }) 26 | 27 | equal('cui__preview custom', preview.props.className) 28 | }) 29 | 30 | it('allows passing children', () => { 31 | const preview = render({}, 'Test') 32 | 33 | equal('Test', preview.props.children.props.children) 34 | }) 35 | }) 36 | 37 | describe('PreviewTitle', () => { 38 | const render = renderer(PreviewTitle) 39 | 40 | describe('default', () => { 41 | const previewTitle = render() 42 | 43 | it("renders tag 'h2'", () => { 44 | equal('h2', previewTitle.type) 45 | }) 46 | 47 | it("has className 'cui__preview__title'", () => { 48 | equal('cui__preview__title', previewTitle.props.className) 49 | }) 50 | }) 51 | 52 | it("allows passing custom 'className' without overriding defaults", () => { 53 | const previewTitle = render({ className: 'custom' }) 54 | 55 | equal('cui__preview__title custom', previewTitle.props.className) 56 | }) 57 | 58 | it('allows passing children', () => { 59 | const previewTitle = render({}, 'Test') 60 | 61 | equal('Test', previewTitle.props.children) 62 | }) 63 | }) 64 | 65 | describe('PreviewLink', () => { 66 | const render = renderer(PreviewLink) 67 | const link = (component) => component.props.children 68 | 69 | describe('default', () => { 70 | const previewLink = render() 71 | 72 | it("renders tag 'div'", () => { 73 | equal('div', previewLink.type) 74 | }) 75 | 76 | it("has className 'cui__preview__footer'", () => { 77 | equal('cui__preview__footer', previewLink.props.className) 78 | }) 79 | 80 | it('contains link', () => { 81 | equal('a', link(previewLink).type) 82 | equal('cui__preview__footer__link', link(previewLink).props.className) 83 | }) 84 | }) 85 | 86 | it("allows passing custom 'className' without overriding link defaults", () => { 87 | const previewLink = render({ className: 'custom' }) 88 | 89 | equal('cui__preview__footer__link custom', link(previewLink).props.className) 90 | }) 91 | 92 | it('allows passing other props to link', () => { 93 | const previewLink = render({ href: '#test', rel: 'test' }) 94 | 95 | equal('#test', link(previewLink).props.href) 96 | equal('test', link(previewLink).props.rel) 97 | }) 98 | 99 | it('allows passing children', () => { 100 | const previewLink = render({}, 'Test') 101 | 102 | equal('Test', previewLink.props.children.props.children) 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /tests/PrimaryTitle.spec.jsx: -------------------------------------------------------------------------------- 1 | import PrimaryTitle from '../components/texts/PrimaryTitle' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import describePalette from './describePalette' 5 | 6 | const render = renderer(PrimaryTitle) 7 | 8 | describe('PrimaryTitle', () => { 9 | describe('default', () => { 10 | const primaryTitle = render({}, 'LoremIpsum') 11 | 12 | it("renders tag 'h1'", () => { 13 | equal(primaryTitle.type, 'h1') 14 | }) 15 | 16 | it("has className 'cui__title--primary'", () => { 17 | ok(primaryTitle.props.className.match('cui__title--primary')) 18 | }) 19 | 20 | it('should have the content', () => { 21 | ok(primaryTitle.props.children.match('LoremIpsum')) 22 | }) 23 | }) 24 | 25 | describePalette(render) 26 | 27 | describe('small', () => { 28 | const primaryTitle = render({small: true}, 'LoremIpsum') 29 | 30 | it('does have class small', () => { 31 | ok(primaryTitle.props.className.match('small')) 32 | }) 33 | }) 34 | 35 | describe('strong', () => { 36 | const primaryTitle = render({strong: true}, 'LoremIpsum') 37 | 38 | it('does have class strong', () => { 39 | ok(primaryTitle.props.className.match('strong')) 40 | }) 41 | }) 42 | 43 | describe('margins', () => { 44 | const primaryTitle = render({margins: true}, 'LoremIpsum') 45 | 46 | it("has class 'default-margins'", () => { 47 | ok(primaryTitle.props.className.match('default-margins')) 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /tests/RadioGroup.spec.jsx: -------------------------------------------------------------------------------- 1 | import RadioGroup from '../components/RadioGroup' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import { spy } from 'sinon' 5 | 6 | const render = renderer(RadioGroup) 7 | const data = [ 8 | {id: 1, label: 'option1', description: 'description1'}, 9 | {id: 2, label: 'option2'} 10 | ] 11 | 12 | describe('RadioGroup', () => { 13 | describe('default', () => { 14 | const onChange = spy() 15 | const radioGroup = render({ data, onChange, selected: 2 }) 16 | const option = radioGroup.props.children 17 | const label = (option) => option.props.children[0] 18 | const description = (option) => option.props.children[1] 19 | 20 | it("renders tag 'div'", () => { 21 | equal(radioGroup.type, 'div') 22 | }) 23 | 24 | it("has className 'cui__dropdown--radio'", () => { 25 | equal(radioGroup.props.className, 'cui__dropdown--radio') 26 | }) 27 | 28 | it('first option is not selected', () => { 29 | equal(option[0].props.className, 'cui__dropdown--radio__option') 30 | }) 31 | 32 | it('first option has label and description', () => { 33 | equal(label(option[0]).props.className, 'cui__dropdown--radio__option__heading') 34 | equal(label(option[0]).props.children, 'option1') 35 | equal(description(option[0]).props.className, 'cui__dropdown--radio__option__description') 36 | equal(description(option[0]).props.children, 'description1') 37 | }) 38 | 39 | it('second option is selected', () => { 40 | equal(option[1].props.className, 'cui__dropdown--radio__option is-selected') 41 | }) 42 | 43 | it('second option has label and no description', () => { 44 | equal(label(option[1]).props.className, 'cui__dropdown--radio__option__heading') 45 | equal(label(option[1]).props.children, 'option2') 46 | equal(description(option[1]), undefined) 47 | }) 48 | 49 | it('calls onChange callback when options are clicked', () => { 50 | option[0].props.onClick() 51 | ok(onChange.calledWith(1)) 52 | option[1].props.onClick() 53 | ok(onChange.calledWith(2)) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tests/SecondaryTitle.spec.jsx: -------------------------------------------------------------------------------- 1 | import SecondaryTitle from '../components/texts/SecondaryTitle' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import describePalette from './describePalette' 5 | 6 | const render = renderer(SecondaryTitle) 7 | 8 | describe('SecondaryTitle', () => { 9 | describe('default', () => { 10 | const secondaryTitle = render({}, 'LoremIpsum') 11 | 12 | it("renders tag 'h2'", () => { 13 | equal(secondaryTitle.type, 'h2') 14 | }) 15 | 16 | it("has className 'cui__title--secondary'", () => { 17 | ok(secondaryTitle.props.className.match('cui__title--secondary')) 18 | }) 19 | 20 | it('should have the content', () => { 21 | ok(secondaryTitle.props.children.match('LoremIpsum')) 22 | }) 23 | }) 24 | 25 | describePalette(render) 26 | 27 | describe('condensed', () => { 28 | const secondaryTitle = render({condensed: true}, 'LoremIpsum') 29 | 30 | it('does have class condensed', () => { 31 | ok(secondaryTitle.props.className.match('condensed')) 32 | }) 33 | }) 34 | 35 | describe('margins', () => { 36 | const secondaryTitle = render({margins: true}, 'LoremIpsum') 37 | 38 | it("has class 'default-margins'", () => { 39 | ok(secondaryTitle.props.className.match('default-margins')) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/Selector.spec.jsx: -------------------------------------------------------------------------------- 1 | import Selector from '../components/Selector' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import { spy } from 'sinon' 5 | 6 | const render = renderer(Selector) 7 | const data = [ 8 | {id: 1, label: 'option1'}, 9 | {id: 2, label: 'option2'} 10 | ] 11 | 12 | describe('Selector', () => { 13 | describe('default', () => { 14 | const onChange = spy() 15 | const selector = render({ data, onChange, selected: 2 }) 16 | const options = selector.props.children 17 | const label = (option) => option.props.children[0] 18 | const icon = (option) => option.props.children[1] 19 | 20 | it("renders tag 'div'", () => { 21 | equal(selector.type, 'div') 22 | }) 23 | 24 | it("has className 'cui__selector--direct title'", () => { 25 | equal(selector.props.className, 'cui__selector--direct title') 26 | }) 27 | 28 | it('first option is not selected', () => { 29 | equal(icon(options[0]), null) 30 | }) 31 | 32 | it('first option has label', () => { 33 | equal(label(options[0]).props.className, 'cui__selector--direct__label') 34 | }) 35 | 36 | it('second option is selected', () => { 37 | equal(icon(options[1]).props.className, 'cui__selector--direct__icon') 38 | }) 39 | 40 | it('second option has label', () => { 41 | equal(label(options[1]).props.className, 'cui__selector--direct__label') 42 | }) 43 | 44 | it('calls onChange callback when options are clicked', () => { 45 | options[0].props.onClick() 46 | ok(onChange.calledWith(data[0])) 47 | options[1].props.onClick() 48 | ok(onChange.calledWith(data[1])) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/Subtitle.spec.jsx: -------------------------------------------------------------------------------- 1 | import Subtitle from '../components/texts/Subtitle' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | import describePalette from './describePalette' 5 | 6 | const render = renderer(Subtitle) 7 | 8 | describe('Subtitle', () => { 9 | describe('default', () => { 10 | const subtitle = render({}, 'LoremIpsum') 11 | 12 | it("renders tag 'h3'", () => { 13 | equal(subtitle.type, 'h3') 14 | }) 15 | 16 | it("has className 'cui__subtitle'", () => { 17 | ok(subtitle.props.className.match('cui__subtitle')) 18 | }) 19 | 20 | it('should have the content', () => { 21 | ok(subtitle.props.children.match('LoremIpsum')) 22 | }) 23 | }) 24 | 25 | describePalette(render) 26 | 27 | describe('condensed', () => { 28 | const subtitle = render({condensed: true}, 'LoremIpsum') 29 | 30 | it('does have class condensed', () => { 31 | ok(subtitle.props.className.match('condensed')) 32 | }) 33 | }) 34 | 35 | describe('margins', () => { 36 | const subtitle = render({margins: true}, 'LoremIpsum') 37 | 38 | it("has class 'default-margins'", () => { 39 | ok(subtitle.props.className.match('default-margins')) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/TextLabel.spec.jsx: -------------------------------------------------------------------------------- 1 | import TextLabel from '../components/texts/TextLabel' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(TextLabel) 6 | 7 | describe('TextLabel', () => { 8 | describe('default', () => { 9 | const textLabel = render({}, 'LoremIpsum') 10 | 11 | it("renders tag 'h4'", () => { 12 | equal(textLabel.type, 'h4') 13 | }) 14 | 15 | it("has className 'cui__text-label'", () => { 16 | equal(textLabel.props.className, 'cui__text-label') 17 | }) 18 | 19 | it('should have the content', () => { 20 | ok(textLabel.props.children.match('LoremIpsum')) 21 | }) 22 | }) 23 | 24 | describe('margins', () => { 25 | const textLabel = render({margins: true}, 'LoremIpsum') 26 | 27 | it("has class 'default-margins'", () => { 28 | ok(textLabel.props.className.match('default-margins')) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /tests/Tooltip.spec.jsx: -------------------------------------------------------------------------------- 1 | import Tooltip from '../components/Tooltip' 2 | import { ok, equal } from 'assert' 3 | import { renderer } from './helpers' 4 | 5 | const render = renderer(Tooltip) 6 | 7 | describe('Tooltip', () => { 8 | describe('default', () => { 9 | const tooltip = render({}, 'Lorem ipsum') 10 | 11 | it("renders tag 'div'", () => { 12 | equal(tooltip.type, 'div') 13 | }) 14 | 15 | it("has className 'cui__tooltip'", () => { 16 | equal(tooltip.props.className, 'cui__tooltip') 17 | }) 18 | 19 | it('should have the content', () => { 20 | ok(tooltip.props.children.match('Lorem ipsum')) 21 | }) 22 | }) 23 | 24 | describe('arrows', () => { 25 | Tooltip.arrows.forEach((arrow) => { 26 | describe(arrow, () => { 27 | const tooltip = render({ arrow }, 'Toggle me') 28 | 29 | it(`has className '${arrow}'`, () => { 30 | ok(tooltip.props.className.match(arrow)) 31 | }) 32 | }) 33 | }) 34 | }) 35 | 36 | describe('border', () => { 37 | const tooltip = render({ border: true }, 'Lorem ipsum') 38 | 39 | it("has className 'cui__tooltip'", () => { 40 | ok(tooltip.props.className.match('border')) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /tests/decorators/statefulFocus.spec.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import statefulFocus from '../../lib/decorators/statefulFocus' 3 | import { shallow } from '../helpers' 4 | import { equal } from 'assert' 5 | 6 | class TestComponent extends Component { 7 | render () { 8 | return ( 9 | 14 | ) 15 | } 16 | } 17 | 18 | describe('statefulFocus', () => { 19 | const DecoratedComponent = statefulFocus(TestComponent) 20 | 21 | describe('onBlur', () => { 22 | it('set focus to false in the inner component', () => { 23 | const decorated = shallow( 24 | DecoratedComponent, 25 | { onBlur: () => {}, onFocus: () => {} } 26 | ) 27 | 28 | decorated.getRenderOutput().props.onBlur({ target: { value: 'foo' } }) 29 | 30 | equal(decorated.getRenderOutput().props.focus, undefined) 31 | }) 32 | 33 | it('update the state focus value', () => { 34 | const decorated = shallow( 35 | DecoratedComponent, 36 | { onBlur: () => {}, onFocus: () => {} } 37 | ) 38 | 39 | decorated.getRenderOutput().props.onBlur({ target: { value: 'foo' } }) 40 | 41 | equal(decorated.getMountedInstance().state.focus, undefined) 42 | }) 43 | }) 44 | 45 | describe('onFocus', () => { 46 | it('set focus to true in the inner component', () => { 47 | const decorated = shallow( 48 | DecoratedComponent, 49 | { onBlur: () => {}, onFocus: () => {} } 50 | ) 51 | 52 | decorated.getRenderOutput().props.onFocus({ target: { value: 'foo' } }) 53 | 54 | equal(decorated.getRenderOutput().props.focus, true) 55 | }) 56 | 57 | it('update the state focus value', () => { 58 | const decorated = shallow( 59 | DecoratedComponent, 60 | { onBlur: () => {}, onFocus: () => {} } 61 | ) 62 | 63 | decorated.getRenderOutput().props.onFocus({ target: { value: 'foo' } }) 64 | 65 | equal(decorated.getMountedInstance().state.focus, true) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /tests/decorators/statefulValue.spec.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import statefulValue from '../../lib/decorators/statefulValue' 3 | import { shallow } from '../helpers' 4 | import { equal } from 'assert' 5 | 6 | class TestComponent extends Component { 7 | render () { 8 | return ( 9 | 13 | ) 14 | } 15 | } 16 | 17 | describe('statefulValue', () => { 18 | const DecoratedComponent = statefulValue(TestComponent) 19 | 20 | describe('onChange', () => { 21 | it('update the value of the inner component', () => { 22 | const decorated = shallow( 23 | DecoratedComponent, 24 | { onChange: () => { } } 25 | ) 26 | 27 | decorated.getRenderOutput().props.onChange({ target: { value: 'foo' } }) 28 | 29 | equal(decorated.getRenderOutput().props.value, 'foo') 30 | }) 31 | 32 | it('update the state value', () => { 33 | const decorated = shallow( 34 | DecoratedComponent, 35 | { onChange: () => { } } 36 | ) 37 | 38 | decorated.getRenderOutput().props.onChange({ target: { value: 'foo' } }) 39 | 40 | equal(decorated.getMountedInstance().state.value, 'foo') 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /tests/describePalette.es6: -------------------------------------------------------------------------------- 1 | import { ok } from 'assert' 2 | import palette from '../components/texts/palette' 3 | 4 | export default (render) => { 5 | describe('palette', () => { 6 | palette.map((color) => { 7 | it(color, () => { 8 | ok(render({ color }).props.className.match(color)) 9 | }) 10 | }) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /tests/helpers.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRenderer } from 'react-addons-test-utils' 3 | 4 | export const renderer = (Component) => (props = {}, children = null) => ( 5 | shallow(Component, props, children).getRenderOutput() 6 | ) 7 | 8 | export const shallow = (Component, props = {}, children = null) => { 9 | const shallowRenderer = createRenderer() 10 | shallowRenderer.render({children}) 11 | return shallowRenderer 12 | } 13 | 14 | export const instance = (Component) => (props = {}, children = null) => ( 15 | shallow(Component, props, children)._instance._instance 16 | ) 17 | -------------------------------------------------------------------------------- /tests/propTypes/fieldSizeFraction.spec.es6: -------------------------------------------------------------------------------- 1 | import fieldSizeFraction, { FIELD_SIZE_ERRORS } from '../../propTypes/fieldSizeFraction' 2 | import { equal } from 'assert' 3 | 4 | describe('fieldSizeFraction', () => { 5 | describe('random text', () => { 6 | it('returns an error', () => ( 7 | equal( 8 | fieldSizeFraction(3)({ fraction: 'asdfa' }, 'fraction', 'Field').message, 9 | "Invalid prop `fraction` supplied to `Field`: needs to be a fraction, such as `1/3`" 10 | ) 11 | )) 12 | }) 13 | 14 | describe('floats in the values', () => { 15 | it('returns an error', () => ( 16 | equal( 17 | fieldSizeFraction(3)({ fraction: '1.3/6' }, 'fraction', 'Field').message, 18 | "Invalid prop `fraction` supplied to `Field`: fraction values need to be positive integers" 19 | ) 20 | )) 21 | }) 22 | 23 | describe('denominator above threshold', () => { 24 | it('returns an error', () => ( 25 | equal( 26 | fieldSizeFraction(3)({ fraction: '1/5' }, 'fraction', 'Field').message, 27 | "Invalid prop `fraction` supplied to `Field`: values needs to be lower or equal to `3`" 28 | ) 29 | )) 30 | }) 31 | 32 | describe('numerator above denominator', () => { 33 | it('returns an error', () => ( 34 | equal( 35 | fieldSizeFraction(3)({ fraction: '3/2' }, 'fraction', 'Field').message, 36 | "Invalid prop `fraction` supplied to `Field`: numerator needs to be lower or equal to the denominator (`2` in this case)" 37 | ) 38 | )) 39 | }) 40 | 41 | describe('default', () => { 42 | it('returns null', () => ( 43 | equal( 44 | fieldSizeFraction(3)({ fraction: '2/3' }, 'fraction', 'Field'), 45 | null 46 | ) 47 | )) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/propTypes/validateSize.spec.es6: -------------------------------------------------------------------------------- 1 | import validateSize from '../../propTypes/validateSize' 2 | import assert, { ok } from 'assert' 3 | 4 | const invalidSizes = [1, {}, [], '1.', '', 'px', '1p', '%', 'wrong', 'em2'] 5 | const validSizes = [undefined, '0', '5px', '100%', '3.5em', '12rem', '0ex', '7.55cm', '300mm', '2in', '10pt', '3pc', '11ch', '77vh', '0.4vw', '.1vmin', '.2vmax'] 6 | 7 | describe('validateSize', () => { 8 | invalidSizes.map((invalid) => { 9 | it(`rejects invalid size '${invalid}'`, () => { 10 | ok(validateSize({size: invalid}, 'size', 'Test') instanceof Error) 11 | assert.deepEqual( 12 | validateSize({size: invalid}, 'size', 'Test').message, 13 | `size '${invalid}' in Test is not valid.` 14 | ) 15 | }) 16 | }) 17 | 18 | validSizes.map((valid) => { 19 | it(`accepts valid size '${valid}'`, () => { 20 | assert(validateSize({size: valid}, 'size') === null) 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/uncontrolled/RadioGroup.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * The default behaviour here is defined by 3 | * RadioGroup, so the tests focus on 4 | * the state of the object 5 | */ 6 | 7 | import StatefulRadioGroup from '../../components/uncontrolled/RadioGroup' 8 | import { equal } from 'assert' 9 | import { shallow } from '../helpers' 10 | 11 | const data = [ 12 | {id: 1, label: 'option1'}, 13 | {id: 2, label: 'option2'} 14 | ] 15 | 16 | const renderer = shallow(StatefulRadioGroup, { data, name: 'lorem' }) 17 | const radioGroup = () => renderer.getRenderOutput().props.children[0] 18 | const input = () => renderer.getRenderOutput().props.children[1] 19 | 20 | describe('StatefulRadioGroup', () => { 21 | it("changes the radio group selected when it's changed", () => { 22 | equal(radioGroup().props.selected, 1) 23 | equal(input().props.value, 1) 24 | radioGroup().props.onChange(2) 25 | equal(radioGroup().props.selected, 2) 26 | equal(input().props.value, 2) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var Webpack = require('webpack') 3 | var WebpackHtmlWebpackPlugin = require('html-webpack-plugin') 4 | var WebpackErrorNotificationPlugin = require('webpack-error-notification') 5 | 6 | module.exports = { 7 | cache: true, 8 | debug: true, 9 | devtool: 'source-map', 10 | entry: { 11 | example: './example/index' 12 | }, 13 | output: { 14 | path: './', 15 | filename: '[name].js', 16 | chunkFilename: '[id].js', 17 | publicPath: '/' 18 | }, 19 | module: { 20 | loaders: [ 21 | { 22 | test: /\.(jsx|es6)$/, 23 | loader: 'babel' 24 | }, 25 | { 26 | test: /\.scss$/, 27 | loaders: [ 28 | 'style', 29 | 'css?modules,localIdentName=[local]', 30 | 'sass' 31 | ] 32 | }, 33 | { 34 | test: /\.css$/, 35 | loaders: [ 36 | 'style', 37 | 'css?modules', 38 | 'autoprefixer' 39 | ] 40 | }, 41 | { 42 | test: /\.(jpe?g|png|gif|svg|ico|eot|woff|ttf|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, 43 | loader: 'file' 44 | } 45 | 46 | ] 47 | }, 48 | plugins: [ 49 | new WebpackErrorNotificationPlugin(), 50 | new Webpack.NoErrorsPlugin(), 51 | new WebpackHtmlWebpackPlugin({ 52 | title: 'UI React components example', 53 | template: 'example/index.html' 54 | }) 55 | ], 56 | resolveLoader: { 57 | fallback: [path.join(__dirname, 'node_modules')] 58 | }, 59 | resolve: { 60 | fallback: [path.join(__dirname, 'node_modules')], 61 | modulesDirectories: [ 62 | './node_modules' 63 | ], 64 | root: path.join(__dirname), 65 | extensions: ['', '.js', '.jsx', '.es6'] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /webpack.config.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var path = require('path') 3 | var devServerPort = 3808 4 | var devServerHostname = '127.0.0.1' 5 | 6 | var config = { 7 | output: { 8 | filename: 'test.build.js', 9 | path: 'tests/', 10 | publicPath: 'http://' + devServerHostname + ':' + devServerPort + '/tests' 11 | }, 12 | resolve: { 13 | fallback: [path.join(__dirname, 'node_modules')], 14 | extensions: ['', '.js', '.jsx', '.es6'], 15 | root: path.join(__dirname) 16 | }, 17 | resolveLoader: { 18 | fallback: [path.join(__dirname, 'node_modules')] 19 | }, 20 | module: { 21 | loaders: [ 22 | { 23 | test: /\.(jsx|es6)$/, 24 | loader: 'babel' 25 | }, 26 | { 27 | test: /\.scss$/, 28 | loaders: [ 29 | 'style', 30 | 'css?modules,localIdentName=[local]', 31 | 'sass' 32 | ] 33 | }, 34 | { 35 | test: /\.(jpe?g|png|gif|svg|ico|eot|woff|ttf|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, 36 | loader: 'null' 37 | } 38 | ] 39 | }, 40 | devServer: { 41 | host: devServerHostname, 42 | port: devServerPort 43 | } 44 | } 45 | 46 | module.exports = config 47 | --------------------------------------------------------------------------------